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