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.metamodel.specloader;
021
022import static org.apache.isis.core.commons.ensure.Ensure.ensureThatArg;
023import static org.hamcrest.CoreMatchers.is;
024import static org.hamcrest.CoreMatchers.notNullValue;
025
026import java.util.ArrayList;
027import java.util.Collection;
028import java.util.Collections;
029import java.util.List;
030import java.util.Map;
031import java.util.Set;
032
033import com.google.common.base.Function;
034import com.google.common.collect.Lists;
035import com.google.common.collect.Maps;
036
037import org.slf4j.Logger;
038import org.slf4j.LoggerFactory;
039
040import org.apache.isis.applib.DomainObjectContainer;
041import org.apache.isis.core.commons.authentication.AuthenticationSessionProvider;
042import org.apache.isis.core.commons.components.ApplicationScopedComponent;
043import org.apache.isis.core.commons.config.IsisConfiguration;
044import org.apache.isis.core.commons.debug.DebugBuilder;
045import org.apache.isis.core.commons.debug.DebuggableWithTitle;
046import org.apache.isis.core.commons.ensure.Assert;
047import org.apache.isis.core.commons.exceptions.IsisException;
048import org.apache.isis.core.commons.lang.ClassUtil;
049import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
050import org.apache.isis.core.metamodel.adapter.ServicesProvider;
051import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager;
052import org.apache.isis.core.metamodel.deployment.DeploymentCategory;
053import org.apache.isis.core.metamodel.facetapi.Facet;
054import org.apache.isis.core.metamodel.facetdecorator.FacetDecorator;
055import org.apache.isis.core.metamodel.facetdecorator.FacetDecoratorSet;
056import org.apache.isis.core.metamodel.facets.object.bounded.ChoicesFacetUtils;
057import org.apache.isis.core.metamodel.facets.object.objecttype.ObjectSpecIdFacet;
058import org.apache.isis.core.metamodel.progmodel.ProgrammingModel;
059import org.apache.isis.core.metamodel.runtimecontext.RuntimeContext;
060import org.apache.isis.core.metamodel.runtimecontext.RuntimeContextAware;
061import org.apache.isis.core.metamodel.runtimecontext.ServicesInjector;
062import org.apache.isis.core.metamodel.runtimecontext.noruntime.RuntimeContextNoRuntime;
063import org.apache.isis.core.metamodel.spec.FreeStandingList;
064import org.apache.isis.core.metamodel.spec.ObjectInstantiator;
065import org.apache.isis.core.metamodel.spec.ObjectSpecId;
066import org.apache.isis.core.metamodel.spec.ObjectSpecification;
067import org.apache.isis.core.metamodel.spec.SpecificationContext;
068import org.apache.isis.core.metamodel.spec.SpecificationLoader;
069import org.apache.isis.core.metamodel.spec.SpecificationLoaderAware;
070import org.apache.isis.core.metamodel.spec.SpecificationLoaderSpi;
071import org.apache.isis.core.metamodel.spec.SpecificationLoaderSpiAware;
072import org.apache.isis.core.metamodel.spec.feature.ObjectMemberContext;
073import org.apache.isis.core.metamodel.specloader.classsubstitutor.ClassSubstitutor;
074import org.apache.isis.core.metamodel.specloader.collectiontyperegistry.CollectionTypeRegistry;
075import org.apache.isis.core.metamodel.specloader.collectiontyperegistry.CollectionTypeRegistryDefault;
076import org.apache.isis.core.metamodel.specloader.facetprocessor.FacetProcessor;
077import org.apache.isis.core.metamodel.specloader.specimpl.CreateObjectContext;
078import org.apache.isis.core.metamodel.specloader.specimpl.FacetedMethodsBuilderContext;
079import org.apache.isis.core.metamodel.specloader.specimpl.IntrospectionContext;
080import org.apache.isis.core.metamodel.specloader.specimpl.ObjectSpecificationAbstract;
081import org.apache.isis.core.metamodel.specloader.specimpl.ObjectSpecificationAbstract.IntrospectionState;
082import org.apache.isis.core.metamodel.specloader.specimpl.dflt.ObjectSpecificationDefault;
083import org.apache.isis.core.metamodel.specloader.specimpl.objectlist.ObjectSpecificationForFreeStandingList;
084import org.apache.isis.core.metamodel.specloader.traverser.SpecificationTraverser;
085import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidator;
086import org.apache.isis.core.metamodel.specloader.validator.ValidationFailures;
087import org.apache.isis.progmodels.dflt.ProgrammingModelFacetsJava5;
088
089/**
090 * Builds the meta-model.
091 * 
092 * <p>
093 * The implementation provides for a degree of pluggability:
094 * <ul>
095 * <li>The most important plug-in point is {@link ProgrammingModel} that
096 * specifies the set of {@link Facet} that make up programming model. If not
097 * specified then defaults to {@link ProgrammingModelFacetsJava5} (which should
098 * be used as a starting point for your own customizations).
099 * <li>The only mandatory plug-in point is {@link ClassSubstitutor}, which
100 * allows the class to be loaded to be substituted if required. This is used in
101 * conjunction with some <tt>PersistenceMechanism</tt>s that do class
102 * enhancement.
103 * <li>The {@link CollectionTypeRegistry} specifies the types that should be
104 * considered as collections. If not specified then will
105 * {@link CollectionTypeRegistryDefault default}. (Note: this extension point
106 * has not been tested, so should be considered more of a &quot;statement of
107 * intent&quot; than actual API. Also, we may use annotations (similar to the
108 * way in which Values are specified) as an alternative mechanism).
109 * </ul>
110 * 
111 * <p>
112 * In addition, the {@link RuntimeContext} can optionally be injected, but will
113 * default to {@link RuntimeContextNoRuntime} if not provided prior to
114 * {@link #init() initialization}. The purpose of {@link RuntimeContext} is to
115 * allow the metamodel to be used standalone, for example in a Maven plugin. The
116 * {@link RuntimeContextNoRuntime} implementation will through an exception for
117 * any methods (such as finding an {@link ObjectAdapter adapter}) because there
118 * is no runtime session. In the case of the metamodel being used by the
119 * framework (that is, when there <i>is</i> a runtime), then the framework
120 * injects an implementation of {@link RuntimeContext} that acts like a bridge
121 * to its <tt>IsisContext</tt>.
122 */
123
124public final class ObjectReflectorDefault implements SpecificationLoaderSpi, ApplicationScopedComponent, RuntimeContextAware, DebuggableWithTitle {
125
126    private final static Logger LOG = LoggerFactory.getLogger(ObjectReflectorDefault.class);
127
128    /**
129     * Injected in the constructor.
130     */
131    private final IsisConfiguration configuration;
132    /**
133     * Injected in the constructor.
134     */
135    private final ClassSubstitutor classSubstitutor;
136    /**
137     * Injected in the constructor.
138     */
139    private final CollectionTypeRegistry collectionTypeRegistry;
140    /**
141     * Injected in the constructor.
142     */
143    private final ProgrammingModel programmingModel;
144
145    /**
146     * Defaulted in the constructor.
147     */
148    private final FacetProcessor facetProcessor;
149
150    /**
151     * Defaulted in the constructor, so can be added to via
152     * {@link #setFacetDecorators(FacetDecoratorSet)} or
153     * {@link #addFacetDecorator(FacetDecorator)}.
154     * 
155     * <p>
156     * {@link FacetDecorator}s must be added prior to {@link #init()
157     * initialization.}
158     */
159    private final FacetDecoratorSet facetDecoratorSet;
160
161    /**
162     * Can optionally be injected, but will default (to
163     * {@link RuntimeContextNoRuntime}) otherwise.
164     * 
165     * <p>
166     * Should be injected when used by framework, but will default to a no-op
167     * implementation if the metamodel is being used standalone (eg for a
168     * code-generator).
169     */
170    private RuntimeContext runtimeContext;
171
172    private final SpecificationTraverser specificationTraverser;
173
174    /**
175     * @see #setContainer(DomainObjectContainer)
176     */
177    private DomainObjectContainer container;
178    /**
179     * @see #setServices(List)
180     */
181    private List<Object> services;
182
183    private final MetaModelValidator metaModelValidator;
184    private final SpecificationCacheDefault cache = new SpecificationCacheDefault();
185    private final ServiceInitializer serviceInitializer = new ServiceInitializer();
186
187    private boolean initialized = false;
188
189
190    // /////////////////////////////////////////////////////////////
191    // Constructor
192    // /////////////////////////////////////////////////////////////
193
194    public ObjectReflectorDefault(
195            final IsisConfiguration configuration, 
196            final ClassSubstitutor classSubstitutor, 
197            final CollectionTypeRegistry collectionTypeRegistry, 
198            final SpecificationTraverser specificationTraverser,
199            final ProgrammingModel programmingModel, 
200            final Set<FacetDecorator> facetDecorators, 
201            final MetaModelValidator metaModelValidator) {
202
203        ensureThatArg(configuration, is(notNullValue()));
204        ensureThatArg(classSubstitutor, is(notNullValue()));
205        ensureThatArg(collectionTypeRegistry, is(notNullValue()));
206        ensureThatArg(specificationTraverser, is(notNullValue()));
207        ensureThatArg(programmingModel, is(notNullValue()));
208        ensureThatArg(facetDecorators, is(notNullValue()));
209        ensureThatArg(metaModelValidator, is(notNullValue()));
210
211        this.configuration = configuration;
212        this.classSubstitutor = classSubstitutor;
213        this.collectionTypeRegistry = collectionTypeRegistry;
214        this.programmingModel = programmingModel;
215        this.specificationTraverser = specificationTraverser;
216
217        this.facetDecoratorSet = new FacetDecoratorSet();
218        for (final FacetDecorator facetDecorator : facetDecorators) {
219            this.facetDecoratorSet.add(facetDecorator);
220        }
221
222        this.metaModelValidator = metaModelValidator;
223        this.facetProcessor = new FacetProcessor(configuration, collectionTypeRegistry, programmingModel);
224    }
225
226    @Override
227    protected void finalize() throws Throwable {
228        super.finalize();
229        LOG.info("finalizing reflector factory " + this);
230    }
231
232    // /////////////////////////////////////////////////////////////
233    // init, shutdown
234    // /////////////////////////////////////////////////////////////
235
236    /**
237     * Initializes and wires up, and primes the cache based on any service
238     * classes that may have been {@link #setServices(List) injected}.
239     */
240    @Override
241    public void init() {
242
243        ValidationFailures validationFailures = initAndValidate();
244        
245        validationFailures.assertNone();
246        
247        cacheBySpecId();
248        
249        initialized = true;
250    }
251
252    @Override
253    public boolean isInitialized() {
254        return initialized;
255    }
256
257    /**
258     * For benefit of <tt>IsisMetaModel</tt>.
259     */
260    public ValidationFailures initAndValidate() {
261        if (LOG.isDebugEnabled()) {
262            LOG.debug("initialising " + this);
263        }
264
265        // default subcomponents
266        if (runtimeContext == null) {
267            runtimeContext = new RuntimeContextNoRuntime();
268        }
269        injectInto(runtimeContext);
270        injectInto(specificationTraverser);
271        injectInto(metaModelValidator);
272
273        // wire subcomponents into each other
274        runtimeContext.injectInto(facetProcessor);
275
276        // initialize subcomponents
277        facetDecoratorSet.init();
278        classSubstitutor.init();
279        collectionTypeRegistry.init();
280        specificationTraverser.init();
281        programmingModel.init();
282        facetProcessor.init();
283        metaModelValidator.init();
284
285        primeCache();
286        
287        ValidationFailures validationFailures = new ValidationFailures();
288        metaModelValidator.validate(validationFailures);
289        return validationFailures;
290    }
291
292        private void cacheBySpecId() {
293                final Map<ObjectSpecId, ObjectSpecification> specById = Maps.newHashMap();
294        for (final ObjectSpecification objSpec : allSpecifications()) {
295            final ObjectSpecId objectSpecId = objSpec.getSpecId();
296            if (objectSpecId == null) {
297                continue;
298            }
299            specById.put(objectSpecId, objSpec);
300        }
301
302        getCache().setCacheBySpecId(specById);
303        }
304
305    /**
306     * load the service specifications and then, using the
307     * {@link #getSpecificationTraverser() traverser}, keep loading all
308     * referenced specifications until we can find no more.
309     */
310    private void primeCache() {
311        for (final Class<?> serviceClass : getServiceClasses()) {
312            internalLoadSpecification(serviceClass);
313        }
314        loadAllSpecifications();
315    }
316
317    private void loadAllSpecifications() {
318        List<Class<?>> newlyDiscoveredClasses = newlyDiscoveredClasses();
319
320        while (newlyDiscoveredClasses.size() > 0) {
321            for (final Class<?> newClass : newlyDiscoveredClasses) {
322                internalLoadSpecification(newClass);
323            }
324            newlyDiscoveredClasses = newlyDiscoveredClasses();
325        }
326    }
327
328    private List<Class<?>> newlyDiscoveredClasses() {
329        final List<Class<?>> newlyDiscoveredClasses = new ArrayList<Class<?>>();
330
331        final Collection<ObjectSpecification> noSpecs = allSpecifications();
332        try {
333            for (final ObjectSpecification noSpec : noSpecs) {
334                getSpecificationTraverser().traverseReferencedClasses(noSpec, newlyDiscoveredClasses);
335            }
336        } catch (final ClassNotFoundException ex) {
337            throw new IsisException(ex);
338        }
339        return newlyDiscoveredClasses;
340    }
341
342    
343    
344    @Override
345    public void shutdown() {
346        LOG.info("shutting down " + this);
347
348        initialized = false;
349        
350        getCache().clear();
351        facetDecoratorSet.shutdown();
352    }
353
354
355    @Override
356    public void invalidateCacheFor(Object domainObject) {
357        invalidateCache(domainObject.getClass());
358    }
359
360    @Override
361    public void invalidateCache(final Class<?> cls) {
362        
363        if(!getCache().isInitialized()) {
364            // could be called by JRebel plugin, before we are up-and-running
365            // just ignore.
366            return;
367        }
368        final Class<?> substitutedType = getClassSubstitutor().getClass(cls);
369        
370        if(substitutedType.isAnonymousClass()) {
371            // JRebel plugin might call us... just ignore 'em.
372            return;
373        }
374        
375        ObjectSpecification spec = loadSpecification(substitutedType);
376        while(spec != null) {
377            final Class<?> type = spec.getCorrespondingClass();
378            getCache().remove(type.getName());
379            if(spec.containsDoOpFacet(ObjectSpecIdFacet.class)) {
380                // umm.  Some specs do not have an ObjectSpecIdFacet...
381                recache(spec);
382            }
383            spec = spec.superclass(); 
384        }
385    }
386
387    private void recache(final ObjectSpecification newSpec) {
388        getCache().recache(newSpec);
389    }
390
391    
392
393    // /////////////////////////////////////////////////////////////
394    // install, load, allSpecifications, lookup
395    // /////////////////////////////////////////////////////////////
396
397    /**
398     * API: Return the specification for the specified class of object.
399     */
400    @Override
401    public final ObjectSpecification loadSpecification(final String className) {
402        ensureThatArg(className, is(notNullValue()), "specification class name must be specified");
403
404        try {
405            final Class<?> cls = loadBuiltIn(className);
406            return internalLoadSpecification(cls);
407        } catch (final ClassNotFoundException e) {
408            final ObjectSpecification spec = getCache().get(className);
409            if (spec == null) {
410                throw new IsisException("No such class available: " + className);
411            }
412            return spec;
413        }
414    }
415
416    /**
417     * API: Return specification.
418     */
419    @Override
420    public ObjectSpecification loadSpecification(final Class<?> type) {
421        final ObjectSpecification spec = internalLoadSpecification(type);
422        if(spec == null) {
423            return null;
424        }
425        if(getCache().isInitialized()) {
426            // umm.  It turns out that anonymous inner classes (eg org.estatio.dom.WithTitleGetter$ToString$1)
427            // don't have an ObjectSpecId; hence the guard.
428            if(spec.containsDoOpFacet(ObjectSpecIdFacet.class)) {
429                ObjectSpecId specId = spec.getSpecId();
430                if (getCache().getByObjectType(specId) == null) {
431                    getCache().recache(spec);
432                }
433            }
434        }
435        return spec;
436    }
437
438    private ObjectSpecification internalLoadSpecification(final Class<?> type) {
439        final Class<?> substitutedType = getClassSubstitutor().getClass(type);
440        return substitutedType != null ? loadSpecificationForSubstitutedClass(substitutedType) : null;
441    }
442
443    private ObjectSpecification loadSpecificationForSubstitutedClass(final Class<?> type) {
444        Assert.assertNotNull(type);
445        final String typeName = type.getName();
446
447        final SpecificationCacheDefault specificationCache = getCache();
448        synchronized (specificationCache) {
449            final ObjectSpecification spec = specificationCache.get(typeName);
450            if (spec != null) {
451                return spec;
452            }
453            final ObjectSpecification specification = createSpecification(type);
454            if (specification == null) {
455                throw new IsisException("Failed to create specification for class " + typeName);
456            }
457
458            // put into the cache prior to introspecting, to prevent
459            // infinite loops
460            specificationCache.cache(typeName, specification);
461
462            introspectIfRequired(specification);
463
464            return specification;
465        }
466    }
467
468    /**
469     * Loads the specifications of the specified types except the one specified
470     * (to prevent an infinite loop).
471     */
472    @Override
473    public boolean loadSpecifications(final List<Class<?>> typesToLoad, final Class<?> typeToIgnore) {
474        boolean anyLoadedAsNull = false;
475        for (final Class<?> typeToLoad : typesToLoad) {
476            if (typeToLoad != typeToIgnore) {
477                final ObjectSpecification noSpec = internalLoadSpecification(typeToLoad);
478                final boolean loadedAsNull = (noSpec == null);
479                anyLoadedAsNull = loadedAsNull || anyLoadedAsNull;
480            }
481        }
482        return anyLoadedAsNull;
483    }
484
485    /**
486     * Loads the specifications of the specified types.
487     */
488    @Override
489    public boolean loadSpecifications(final List<Class<?>> typesToLoad) {
490        return loadSpecifications(typesToLoad, null);
491    }
492
493    /**
494     * Creates the appropriate type of {@link ObjectSpecification}.
495     */
496    private ObjectSpecification createSpecification(final Class<?> cls) {
497
498        final AuthenticationSessionProvider authenticationSessionProvider = getRuntimeContext().getAuthenticationSessionProvider();
499        final SpecificationLoader specificationLookup = getRuntimeContext().getSpecificationLoader();
500        final ServicesProvider servicesProvider = getRuntimeContext().getServicesProvider();
501        final ObjectInstantiator objectInstantiator = getRuntimeContext().getObjectInstantiator();
502
503        // create contexts as inputs ...
504        final SpecificationContext specContext = new SpecificationContext(getDeploymentCategory(), authenticationSessionProvider, servicesProvider, objectInstantiator, specificationLookup, facetProcessor);
505
506        final AdapterManager adapterMap = getRuntimeContext().getAdapterManager();
507        final ObjectMemberContext objectMemberContext = new ObjectMemberContext(getDeploymentCategory(), authenticationSessionProvider, specificationLookup, adapterMap, getRuntimeContext().getQuerySubmitter(), collectionTypeRegistry, servicesProvider);
508
509        // ... and create the specs
510        if (FreeStandingList.class.isAssignableFrom(cls)) {
511            return new ObjectSpecificationForFreeStandingList(specContext, objectMemberContext);
512        } else {
513            final SpecificationLoaderSpi specificationLoader = this;
514            final IntrospectionContext introspectionContext = new IntrospectionContext(getClassSubstitutor());
515            final ServicesInjector dependencyInjector = getRuntimeContext().getDependencyInjector();
516            final CreateObjectContext createObjectContext = new CreateObjectContext(adapterMap, dependencyInjector);
517            final FacetedMethodsBuilderContext facetedMethodsBuilderContext = new FacetedMethodsBuilderContext(specificationLoader, classSubstitutor, specificationTraverser, facetProcessor);
518            return new ObjectSpecificationDefault(cls, facetedMethodsBuilderContext, introspectionContext, specContext, objectMemberContext, createObjectContext);
519        }
520    }
521
522    private DeploymentCategory getDeploymentCategory() {
523        if(runtimeContext == null) {
524            throw new IllegalStateException("Runtime context has not been injected.");
525        }
526        return runtimeContext.getDeploymentCategory();
527    }
528
529    private Class<?> loadBuiltIn(final String className) throws ClassNotFoundException {
530        final Class<?> builtIn = ClassUtil.getBuiltIn(className);
531        if (builtIn != null) {
532            return builtIn;
533        }
534        return Class.forName(className);
535    }
536
537    /**
538     * Return all the loaded specifications.
539     */
540    @Override
541    public Collection<ObjectSpecification> allSpecifications() {
542        return getCache().allSpecifications();
543    }
544
545    @Override
546    public boolean loaded(final Class<?> cls) {
547        return loaded(cls.getName());
548    }
549
550    @Override
551    public boolean loaded(final String fullyQualifiedClassName) {
552        return getCache().get(fullyQualifiedClassName) != null;
553    }
554
555    public ObjectSpecification introspectIfRequired(final ObjectSpecification spec) {
556        final ObjectSpecificationAbstract specSpi = (ObjectSpecificationAbstract)spec;
557        final IntrospectionState introspectionState = specSpi.getIntrospectionState();
558
559        if (introspectionState == IntrospectionState.NOT_INTROSPECTED) {
560            specSpi.setIntrospectionState(IntrospectionState.BEING_INTROSPECTED);
561            
562            specSpi.introspectTypeHierarchyAndMembers();
563            facetDecoratorSet.decorate(spec);
564            specSpi.updateFromFacetValues();
565            
566            specSpi.setIntrospectionState(IntrospectionState.INTROSPECTED);
567        } else if (introspectionState == IntrospectionState.BEING_INTROSPECTED) {
568            // nothing to do
569
570            specSpi.introspectTypeHierarchyAndMembers();
571            facetDecoratorSet.decorate(spec);
572            specSpi.updateFromFacetValues();
573            
574            specSpi.setIntrospectionState(IntrospectionState.INTROSPECTED);
575
576        } else if (introspectionState == IntrospectionState.INTROSPECTED) {
577            // nothing to do
578        }
579        return spec;
580    }
581
582    @Override
583    public ObjectSpecification lookupBySpecId(ObjectSpecId objectSpecId) {
584        return getCache().getByObjectType(objectSpecId);
585    }
586
587
588    // ////////////////////////////////////////////////////////////////////
589    // injectInto
590    // ////////////////////////////////////////////////////////////////////
591
592    /**
593     * Injects self into candidate if required, and instructs its subcomponents
594     * to do so also.
595     */
596    @Override
597    public void injectInto(final Object candidate) {
598        final Class<?> candidateClass = candidate.getClass();
599        if (SpecificationLoaderSpiAware.class.isAssignableFrom(candidateClass)) {
600            final SpecificationLoaderSpiAware cast = SpecificationLoaderSpiAware.class.cast(candidate);
601            cast.setSpecificationLoaderSpi(this);
602        }
603        if (SpecificationLoaderAware.class.isAssignableFrom(candidateClass)) {
604            final SpecificationLoaderAware cast = SpecificationLoaderAware.class.cast(candidate);
605            cast.setSpecificationLookup(this);
606        }
607
608        getClassSubstitutor().injectInto(candidate);
609        getCollectionTypeRegistry().injectInto(candidate);
610    }
611
612    // /////////////////////////////////////////////////////////////
613    // Debugging
614    // /////////////////////////////////////////////////////////////
615
616    @Override
617    public void debugData(final DebugBuilder debug) {
618        facetDecoratorSet.debugData(debug);
619        debug.appendln();
620
621        debug.appendTitle("Specifications");
622        final List<ObjectSpecification> specs = Lists.newArrayList(allSpecifications());
623        Collections.sort(specs, ObjectSpecification.COMPARATOR_SHORT_IDENTIFIER_IGNORE_CASE);
624        for (final ObjectSpecification spec : specs) {
625            StringBuffer str = new StringBuffer();
626            str.append(spec.isAbstract() ? "A" : ".");
627            str.append(spec.isService() ? "S" : ".");
628            str.append(ChoicesFacetUtils.hasChoices(spec) ? "B" : ".");
629            str.append(spec.isParentedOrFreeCollection() ? "C" : ".");
630            str.append(spec.isNotCollection() ? "O" : ".");
631            str.append(spec.isParseable() ? "P" : ".");
632            str.append(spec.isEncodeable() ? "E" : ".");
633            str.append(spec.isValueOrIsParented() ? "A" : ".");
634            
635            final boolean hasIdentity = !(spec.isParentedOrFreeCollection() || spec.isParented() || spec.isValue());
636            str.append( hasIdentity ? "I" : ".");
637            str.append("  ");
638            str.append(spec.getFullIdentifier());
639            
640            debug.appendPreformatted(spec.getShortIdentifier(), str.toString());
641        }
642    }
643    
644    @Override
645    public String debugTitle() {
646        return "Reflector";
647    }
648
649    // /////////////////////////////////////////////////////////////
650    // Helpers (were previously injected, but no longer required)
651    // /////////////////////////////////////////////////////////////
652
653    /**
654     * Provides access to the registered {@link Facet}s.
655     */
656    public FacetProcessor getFacetProcessor() {
657        return facetProcessor;
658    }
659
660    private SpecificationCacheDefault getCache() {
661        return cache;
662    }
663
664    // ////////////////////////////////////////////////////////////////////
665    // Dependencies (injected by setter due to *Aware)
666    // ////////////////////////////////////////////////////////////////////
667
668    /**
669     * As per {@link #setRuntimeContext(RuntimeContext)}.
670     */
671    public RuntimeContext getRuntimeContext() {
672        return runtimeContext;
673    }
674
675    /**
676     * Due to {@link RuntimeContextAware}.
677     */
678    @Override
679    public void setRuntimeContext(final RuntimeContext runtimeContext) {
680        this.runtimeContext = runtimeContext;
681    }
682
683    // ////////////////////////////////////////////////////////////////////
684    // Dependencies (setters, optional)
685    // ////////////////////////////////////////////////////////////////////
686
687    public List<Class<?>> getServiceClasses() {
688        List<Class<?>> serviceClasses = Lists.transform(services, new Function<Object, Class<?>>(){
689            public Class<?> apply(Object o) {
690                return o.getClass();
691            }
692        });
693        return Collections.unmodifiableList(serviceClasses);
694    }
695
696    private List<Object> getServices() {
697        return services;
698    }
699    
700    @Override
701    public void setContainer(final DomainObjectContainer container) {
702        this.container = container;
703    }
704    
705    @Override
706    public void setServices(final List<Object> services) {
707        this.services = services;
708    }
709
710    // ////////////////////////////////////////////////////////////////////
711    // Dependencies (injected from constructor)
712    // ////////////////////////////////////////////////////////////////////
713
714    protected IsisConfiguration getIsisConfiguration() {
715        return configuration;
716    }
717
718    protected CollectionTypeRegistry getCollectionTypeRegistry() {
719        return collectionTypeRegistry;
720    }
721
722    protected ClassSubstitutor getClassSubstitutor() {
723        return classSubstitutor;
724    }
725
726    protected SpecificationTraverser getSpecificationTraverser() {
727        return specificationTraverser;
728    }
729
730    protected ProgrammingModel getProgrammingModelFacets() {
731        return programmingModel;
732    }
733
734    protected MetaModelValidator getMetaModelValidator() {
735        return metaModelValidator;
736    }
737
738    protected Set<FacetDecorator> getFacetDecoratorSet() {
739        return facetDecoratorSet.getFacetDecorators();
740    }
741
742    @Override
743    public void validateSpecifications(ValidationFailures validationFailures) {
744        final Map<ObjectSpecId, ObjectSpecification> specById = Maps.newHashMap();
745        for (final ObjectSpecification objSpec : allSpecifications()) {
746            final ObjectSpecId objectSpecId = objSpec.getSpecId();
747            if (objectSpecId == null) {
748                continue;
749            }
750            final ObjectSpecification existingSpec = specById.put(objectSpecId, objSpec);
751            if (existingSpec == null) {
752                continue;
753            }
754            validationFailures.add("Cannot have two entities with same object type (@ObjectType facet or equivalent) Value; " + "both %s and %s are annotated with value of ''%s''.", existingSpec.getFullIdentifier(), objSpec.getFullIdentifier(), objectSpecId);
755        }
756    }
757
758
759
760}