/*
* 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.object.value.vsp;
import java.text.DecimalFormat;
import java.text.Format;
import java.text.NumberFormat;
import java.util.Locale;
import org.apache.isis.applib.adapters.DefaultsProvider;
import org.apache.isis.applib.adapters.EncoderDecoder;
import org.apache.isis.applib.adapters.Parser;
import org.apache.isis.applib.adapters.Parser2;
import org.apache.isis.applib.adapters.ValueSemanticsProvider;
import org.apache.isis.applib.clock.Clock;
import org.apache.isis.core.commons.config.ConfigurationConstants;
import org.apache.isis.core.commons.config.IsisConfiguration;
import org.apache.isis.core.commons.exceptions.UnknownTypeException;
import org.apache.isis.core.commons.lang.LocaleUtil;
import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager;
import org.apache.isis.core.metamodel.facetapi.Facet;
import org.apache.isis.core.metamodel.facetapi.FacetAbstract;
import org.apache.isis.core.metamodel.facetapi.FacetHolder;
import org.apache.isis.core.metamodel.facets.object.parseable.InvalidEntryException;
import org.apache.isis.core.metamodel.facets.properties.defaults.PropertyDefaultFacet;
import org.apache.isis.core.metamodel.services.ServicesInjector;
import org.apache.isis.core.metamodel.spec.ObjectSpecification;
import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
public abstract class ValueSemanticsProviderAndFacetAbstract<T> extends FacetAbstract implements ValueSemanticsProvider<T>, EncoderDecoder<T>, Parser2<T>, DefaultsProvider<T> {
private final Class<T> adaptedClass;
private final int typicalLength;
private final Integer maxLength;
private final boolean immutable;
private final boolean equalByContent;
private final T defaultValue;
public enum Immutability {
IMMUTABLE,
NOT_IMMUTABLE;
public static Immutability of(boolean immutable) {
return immutable? IMMUTABLE: NOT_IMMUTABLE;
}
}
public enum EqualByContent {
HONOURED,
NOT_HONOURED;
public static EqualByContent of(boolean equalByContent) {
return equalByContent? HONOURED: NOT_HONOURED;
}
}
/**
* Lazily looked up per {@link #getSpecification()}.
*/
private ObjectSpecification specification;
private final IsisConfiguration configuration;
private final ServicesInjector context;
public ValueSemanticsProviderAndFacetAbstract(
final Class<? extends Facet> adapterFacetType,
final FacetHolder holder,
final Class<T> adaptedClass,
final int typicalLength,
final Integer maxLength,
final Immutability immutability,
final EqualByContent equalByContent,
final T defaultValue,
final ServicesInjector context) {
super(adapterFacetType, holder, Derivation.NOT_DERIVED);
this.adaptedClass = adaptedClass;
this.typicalLength = typicalLength;
this.maxLength = maxLength;
this.immutable = (immutability == Immutability.IMMUTABLE);
this.equalByContent = (equalByContent == EqualByContent.HONOURED);
this.defaultValue = defaultValue;
this.configuration = context.getConfigurationServiceInternal();
this.context = context;
}
public ObjectSpecification getSpecification() {
if (specification == null) {
specification = getSpecificationLoader().loadSpecification(getAdaptedClass());
}
return specification;
}
/**
* The underlying class that has been adapted.
*
* <p>
* Used to determine whether an empty string can be parsed, (for primitive
* types a non-null entry is required, see {@link #mustHaveEntry()}), and
* potentially useful for debugging.
*/
public final Class<T> getAdaptedClass() {
return adaptedClass;
}
/**
* We don't replace any (none no-op) facets.
*
* <p>
* For example, if there is already a {@link PropertyDefaultFacet} then we
* shouldn't replace it.
*/
@Override
public boolean alwaysReplace() {
return false;
}
// ///////////////////////////////////////////////////////////////////////////
// ValueSemanticsProvider implementation
// ///////////////////////////////////////////////////////////////////////////
@Override
public EncoderDecoder<T> getEncoderDecoder() {
return this;
}
@Override
public Parser<T> getParser() {
return this;
}
@Override
public DefaultsProvider<T> getDefaultsProvider() {
return this;
}
@Override
public boolean isEqualByContent() {
return equalByContent;
}
@Override
public boolean isImmutable() {
return immutable;
}
// ///////////////////////////////////////////////////////////////////////////
// Parser implementation
// ///////////////////////////////////////////////////////////////////////////
@Override
public T parseTextEntry(final Object context, final String entry) {
if (entry == null) {
throw new IllegalArgumentException();
}
if (entry.trim().equals("")) {
if (mustHaveEntry()) {
throw new InvalidEntryException("An entry is required");
} else {
return null;
}
}
return doParse(context, entry);
}
/**
* @param context
* - the underlying object, or <tt>null</tt>.
* @param entry
* - the proposed new object, as a string representation to be
* parsed
*/
protected T doParse(Object context, String entry) {
return doParse(entry, context);
}
// REVIEW: this method used to take Localization as a third param, could now inline
protected T doParse(String entry, Object context) {
return doParse(context, entry);
}
/**
* Whether a non-null entry is required, used by parsing.
*
* <p>
* Adapters for primitives will return <tt>true</tt>.
*/
private final boolean mustHaveEntry() {
return adaptedClass.isPrimitive();
}
@Override
public String displayTitleOf(final Object object) {
if (object == null) {
return "";
}
return titleString(object);
}
@Override
public String displayTitleOf(final Object object, final String usingMask) {
if (object == null) {
return "";
}
return titleStringWithMask(object, usingMask);
}
/**
* Defaults to {@link Parser#displayTitleOf(Object)}.
*/
@Override
public String parseableTitleOf(final Object existing) {
return displayTitleOf(existing);
}
protected String titleString(final Format formatter, final Object object) {
return object == null ? "" : formatter.format(object);
}
/**
* Return a string representation of aforesaid object.
*/
protected abstract String titleString(Object object);
public abstract String titleStringWithMask(final Object value, final String usingMask);
@Override
public final int typicalLength() {
return this.typicalLength;
}
@Override
public final Integer maxLength() {
return this.maxLength;
}
// ///////////////////////////////////////////////////////////////////////////
// DefaultsProvider implementation
// ///////////////////////////////////////////////////////////////////////////
@Override
public T getDefaultValue() {
return this.defaultValue;
}
// ///////////////////////////////////////////////////////////////////////////
// EncoderDecoder implementation
// ///////////////////////////////////////////////////////////////////////////
@Override
public String toEncodedString(final Object object) {
return doEncode(object);
}
@Override
public T fromEncodedString(final String data) {
return doRestore(data);
}
/**
* Hook method to perform the actual encoding.
*/
protected abstract String doEncode(Object object);
/**
* Hook method to perform the actual restoring.
*/
protected abstract T doRestore(String data);
// ///////////////////////////////////////////////////////////////////////////
// Helper: Locale handling
// ///////////////////////////////////////////////////////////////////////////
protected NumberFormat determineNumberFormat(final String suffix) {
final String formatRequired = getConfiguration().getString(ConfigurationConstants.ROOT + suffix);
if (formatRequired != null) {
return new DecimalFormat(formatRequired);
} else {
return NumberFormat.getNumberInstance(findLocale());
}
}
private Locale findLocale() {
final String localeStr = getConfiguration().getString(ConfigurationConstants.ROOT + "locale");
final Locale findLocale = LocaleUtil.findLocale(localeStr);
return findLocale != null ? findLocale : Locale.getDefault();
}
// //////////////////////////////////////////////////////////
// Helper: createAdapter
// //////////////////////////////////////////////////////////
protected ObjectAdapter createAdapter(final Class<?> type, final Object object) {
final ObjectSpecification specification = getSpecificationLoader().loadSpecification(type);
if (specification.isNotCollection()) {
return getAdapterManager().adapterFor(object);
} else {
throw new UnknownTypeException("not an object, is this a collection?");
}
}
// //////////////////////////////////////////////////////////
// Dependencies (from constructor)
// //////////////////////////////////////////////////////////
protected IsisConfiguration getConfiguration() {
return configuration;
}
protected ServicesInjector getContext() {
return context;
}
/**
* From {@link #getContext() context.}
*/
protected AdapterManager getAdapterManager() {
return context.getPersistenceSessionServiceInternal();
}
/**
* From {@link #getContext() context.}
*/
protected SpecificationLoader getSpecificationLoader() {
return context.getSpecificationLoader();
}
/**
* From {@link #getContext() context.}
*/
protected ServicesInjector getServicesInjector() {
return context;
}
// //////////////////////////////////////////////////////////
// Dependencies (from singleton)
// //////////////////////////////////////////////////////////
protected static Clock getClock() {
return Clock.getInstance();
}
}