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    
020    package org.apache.isis.core.progmodel.facets.object.value;
021    
022    import java.text.DecimalFormat;
023    import java.text.Format;
024    import java.text.NumberFormat;
025    import java.util.Locale;
026    
027    import org.apache.isis.applib.adapters.DefaultsProvider;
028    import org.apache.isis.applib.adapters.EncoderDecoder;
029    import org.apache.isis.applib.adapters.Parser;
030    import org.apache.isis.applib.adapters.ValueSemanticsProvider;
031    import org.apache.isis.applib.clock.Clock;
032    import org.apache.isis.applib.profiles.Localization;
033    import org.apache.isis.core.commons.authentication.AuthenticationSessionProvider;
034    import org.apache.isis.core.commons.config.ConfigurationConstants;
035    import org.apache.isis.core.commons.config.IsisConfiguration;
036    import org.apache.isis.core.commons.exceptions.UnknownTypeException;
037    import org.apache.isis.core.commons.lang.LocaleUtils;
038    import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
039    import org.apache.isis.core.metamodel.adapter.map.AdapterMap;
040    import org.apache.isis.core.metamodel.facetapi.Facet;
041    import org.apache.isis.core.metamodel.facetapi.FacetAbstract;
042    import org.apache.isis.core.metamodel.facetapi.FacetHolder;
043    import org.apache.isis.core.metamodel.facets.object.parseable.InvalidEntryException;
044    import org.apache.isis.core.metamodel.facets.properties.defaults.PropertyDefaultFacet;
045    import org.apache.isis.core.metamodel.runtimecontext.DependencyInjector;
046    import org.apache.isis.core.metamodel.spec.ObjectSpecification;
047    import org.apache.isis.core.metamodel.spec.SpecificationLookup;
048    
049    public abstract class ValueSemanticsProviderAndFacetAbstract<T> extends FacetAbstract implements
050        ValueSemanticsProvider<T>, EncoderDecoder<T>, Parser<T>, DefaultsProvider<T> {
051    
052        private final Class<T> adaptedClass;
053        private final int typicalLength;
054        private final boolean immutable;
055        private final boolean equalByContent;
056        private final T defaultValue;
057    
058        /**
059         * Lazily looked up per {@link #getSpecification()}.
060         */
061        private ObjectSpecification specification;
062    
063        private final IsisConfiguration configuration;
064        private final ValueSemanticsProviderContext context;
065    
066        public ValueSemanticsProviderAndFacetAbstract(final Class<? extends Facet> adapterFacetType,
067            final FacetHolder holder, final Class<T> adaptedClass, final int typicalLength, final boolean immutable,
068            final boolean equalByContent, final T defaultValue, final IsisConfiguration configuration,
069            final ValueSemanticsProviderContext context) {
070            super(adapterFacetType, holder, false);
071            this.adaptedClass = adaptedClass;
072            this.typicalLength = typicalLength;
073            this.immutable = immutable;
074            this.equalByContent = equalByContent;
075            this.defaultValue = defaultValue;
076    
077            this.configuration = configuration;
078            this.context = context;
079        }
080    
081        public ObjectSpecification getSpecification() {
082            if (specification == null) {
083                specification = getSpecificationLookup().loadSpecification(getAdaptedClass());
084            }
085            return specification;
086        }
087    
088        /**
089         * The underlying class that has been adapted.
090         * 
091         * <p>
092         * Used to determine whether an empty string can be parsed, (for primitive types a non-null entry is required, see
093         * {@link #mustHaveEntry()}), and potentially useful for debugging.
094         */
095        public final Class<T> getAdaptedClass() {
096            return adaptedClass;
097        }
098    
099        /**
100         * We don't replace any (none no-op) facets.
101         * 
102         * <p>
103         * For example, if there is already a {@link PropertyDefaultFacet} then we shouldn't replace it.
104         */
105        @Override
106        public boolean alwaysReplace() {
107            return false;
108        }
109    
110        // ///////////////////////////////////////////////////////////////////////////
111        // ValueSemanticsProvider implementation
112        // ///////////////////////////////////////////////////////////////////////////
113    
114        @Override
115        public EncoderDecoder<T> getEncoderDecoder() {
116            return this;
117        }
118    
119        @Override
120        public Parser<T> getParser() {
121            return this;
122        }
123    
124        @Override
125        public DefaultsProvider<T> getDefaultsProvider() {
126            return this;
127        }
128    
129        @Override
130        public boolean isEqualByContent() {
131            return equalByContent;
132        }
133    
134        @Override
135        public boolean isImmutable() {
136            return immutable;
137        }
138    
139        // ///////////////////////////////////////////////////////////////////////////
140        // Parser implementation
141        // ///////////////////////////////////////////////////////////////////////////
142    
143        @Override
144        public T parseTextEntry(final Object context, final String entry) {
145            if (entry == null) {
146                throw new IllegalArgumentException();
147            }
148            if (entry.trim().equals("")) {
149                if (mustHaveEntry()) {
150                    throw new InvalidEntryException("An entry is required");
151                } else {
152                    return null;
153                }
154            }
155            return doParse(context, entry);
156        }
157    
158        /**
159         * @param context
160         *            - the underlying object, or <tt>null</tt>.
161         * @param entry
162         *            - the proposed new object, as a string representation to be parsed
163         */
164        protected abstract T doParse(Object context, String entry);
165    
166        /**
167         * Whether a non-null entry is required, used by parsing.
168         * 
169         * <p>
170         * Adapters for primitives will return <tt>true</tt>.
171         */
172        private final boolean mustHaveEntry() {
173            return adaptedClass.isPrimitive();
174        }
175    
176        @Override
177        public String displayTitleOf(final Object object, final Localization localization) {
178            if (object == null) {
179                return "";
180            }
181            return titleString(object, localization);
182        }
183    
184        @Override
185        public String displayTitleOf(final Object object, final String usingMask) {
186            if (object == null) {
187                return "";
188            }
189            return titleStringWithMask(object, usingMask);
190        }
191    
192        /**
193         * Defaults to {@link #displayTitleOf(Object, Localization)}.
194         */
195        @Override
196        public String parseableTitleOf(final Object existing) {
197            return displayTitleOf(existing, (Localization) null);
198        }
199    
200        protected String titleString(final Format formatter, final Object object) {
201            return object == null ? "" : formatter.format(object);
202        }
203    
204        /**
205         * Return a string representation of aforesaid object.
206         */
207        protected abstract String titleString(Object object, Localization localization);
208    
209        public abstract String titleStringWithMask(final Object value, final String usingMask);
210    
211        @Override
212        public final int typicalLength() {
213            return this.typicalLength;
214        }
215    
216        // ///////////////////////////////////////////////////////////////////////////
217        // DefaultsProvider implementation
218        // ///////////////////////////////////////////////////////////////////////////
219    
220        @Override
221        public T getDefaultValue() {
222            return this.defaultValue;
223        }
224    
225        // ///////////////////////////////////////////////////////////////////////////
226        // EncoderDecoder implementation
227        // ///////////////////////////////////////////////////////////////////////////
228    
229        @Override
230        public String toEncodedString(final Object object) {
231            return doEncode(object);
232        }
233    
234        @Override
235        public T fromEncodedString(final String data) {
236            return doRestore(data);
237        }
238    
239        /**
240         * Hook method to perform the actual encoding.
241         */
242        protected abstract String doEncode(Object object);
243    
244        /**
245         * Hook method to perform the actual restoring.
246         */
247        protected abstract T doRestore(String data);
248    
249        // ///////////////////////////////////////////////////////////////////////////
250        // Helper: Locale handling
251        // ///////////////////////////////////////////////////////////////////////////
252    
253        protected NumberFormat determineNumberFormat(final String suffix) {
254            final String formatRequired = getConfiguration().getString(ConfigurationConstants.ROOT + suffix);
255            if (formatRequired != null) {
256                return new DecimalFormat(formatRequired);
257            } else {
258                return NumberFormat.getNumberInstance(findLocale());
259            }
260        }
261    
262        private Locale findLocale() {
263            final String localeStr = getConfiguration().getString(ConfigurationConstants.ROOT + "locale");
264    
265            final Locale findLocale = LocaleUtils.findLocale(localeStr);
266            return findLocale != null ? findLocale : Locale.getDefault();
267        }
268    
269        // //////////////////////////////////////////////////////////
270        // Helper: createAdapter
271        // //////////////////////////////////////////////////////////
272    
273        protected ObjectAdapter createAdapter(final Class<?> type, final Object object) {
274            final ObjectSpecification specification = getSpecificationLookup().loadSpecification(type);
275            if (specification.isNotCollection()) {
276                return getAdapterMap().adapterFor(object);
277            } else {
278                throw new UnknownTypeException("not an object, is this a collection?");
279            }
280        }
281    
282        // //////////////////////////////////////////////////////////
283        // Dependencies (from constructor)
284        // //////////////////////////////////////////////////////////
285    
286        protected IsisConfiguration getConfiguration() {
287            return configuration;
288        }
289    
290        protected ValueSemanticsProviderContext getContext() {
291            return context;
292        }
293    
294        /**
295         * From {@link #getContext() context.}
296         */
297        protected AdapterMap getAdapterMap() {
298            return context.getAdapterMap();
299        }
300    
301        /**
302         * From {@link #getContext() context.}
303         */
304        protected SpecificationLookup getSpecificationLookup() {
305            return context.getSpecificationLookup();
306        }
307    
308        /**
309         * From {@link #getContext() context.}
310         */
311        protected DependencyInjector getDependencyInjector() {
312            return context.getDependencyInjector();
313        }
314    
315        /**
316         * From {@link #getContext() context.}
317         */
318        protected AuthenticationSessionProvider getAuthenticationSessionProvider() {
319            return context.getAuthenticationSessionProvider();
320        }
321    
322        // //////////////////////////////////////////////////////////
323        // Dependencies (from singleton)
324        // //////////////////////////////////////////////////////////
325    
326        protected static Clock getClock() {
327            return Clock.getInstance();
328        }
329    
330    }