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 }