/*
* 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.spec.feature;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.isis.applib.annotation.When;
import org.apache.isis.applib.annotation.Where;
import org.apache.isis.applib.filter.Filter;
import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
import org.apache.isis.core.metamodel.consent.Consent;
import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy;
import org.apache.isis.core.metamodel.facetapi.Facet;
import org.apache.isis.core.metamodel.facets.WhenAndWhereValueFacet;
import org.apache.isis.core.metamodel.facets.all.hide.HiddenFacet;
import org.apache.isis.core.metamodel.facets.members.order.MemberOrderFacet;
import org.apache.isis.core.metamodel.facets.object.membergroups.MemberGroupLayoutFacet;
import org.apache.isis.core.metamodel.facets.object.value.ValueFacet;
import org.apache.isis.core.metamodel.layout.memberorderfacet.MemberOrderComparator;
import org.apache.isis.core.metamodel.specloader.specimpl.ContributeeMember;
import org.apache.isis.core.metamodel.util.DeweyOrderComparator;
/**
* Provides reflective access to a field on a domain object.
*/
public interface ObjectAssociation extends ObjectMember, CurrentHolder {
/**
* As per {@link #get(ObjectAdapter, InteractionInitiatedBy)}, with {@link InteractionInitiatedBy#USER}.
*/
ObjectAdapter get(final ObjectAdapter owner);
/**
* Returns the referenced {@link ObjectAdapter} for the owning
* {@link ObjectAdapter}.
*
* <p>
* For example, if this is an {@link OneToOneAssociation}, then returns the
* referenced object.
*/
ObjectAdapter get(final ObjectAdapter owner, final InteractionInitiatedBy interactionInitiatedBy);
/**
* Return the default for this property.
*/
ObjectAdapter getDefault(ObjectAdapter adapter);
/**
* Set the property to it default references/values.
*/
public void toDefault(ObjectAdapter target);
/**
* Whether there are any choices provided (eg <tt>choicesXxx</tt> supporting
* method) for the association.
*/
public boolean hasChoices();
/**
* Returns a list of possible references/values for this field, which the
* user can choose from.
*/
public ObjectAdapter[] getChoices(
final ObjectAdapter object,
final InteractionInitiatedBy interactionInitiatedBy);
/**
* Whether there are any auto-complete provided (eg <tt>autoCompleteXxx</tt> supporting
* method) for the association.
*/
public boolean hasAutoComplete();
/**
* Returns a list of possible references/values for this field, which the
* user can choose from, based on the provided search argument.
*/
public ObjectAdapter[] getAutoComplete(
final ObjectAdapter object,
final String searchArg,
final InteractionInitiatedBy interactionInitiatedBy);
int getAutoCompleteMinLength();
/**
* Returns true if calculated from other data in the object, that is, should
* not be persisted.
*/
boolean isNotPersisted();
/**
* Returns <code>true</code> if this field on the specified object is deemed
* to be empty, or has no content.
*/
boolean isEmpty(ObjectAdapter target, final InteractionInitiatedBy interactionInitiatedBy);
/**
* Determines if this field must be complete before the object is in a valid
* state
*/
boolean isMandatory();
// //////////////////////////////////////////////////////
// Functions
// //////////////////////////////////////////////////////
public static class Functions {
private Functions(){}
public static Function<ObjectAssociation, String> toName() {
return new Function<ObjectAssociation, String>() {
@Override
public String apply(final ObjectAssociation oa) {
return oa.getName();
}
};
}
public static Function<ObjectAssociation, String> toId() {
return new Function<ObjectAssociation, String>() {
@Override
public String apply(final ObjectAssociation oa) {
return oa.getId();
}
};
}
}
// //////////////////////////////////////////////////////
// Predicates
// //////////////////////////////////////////////////////
public static class Predicates {
private Predicates(){}
public static Predicate<ObjectAssociation> being(final Contributed contributed) {
return new Predicate<ObjectAssociation>(){
@Override
public boolean apply(final ObjectAssociation t) {
return contributed.isIncluded() ||
!(t instanceof ContributeeMember);
}
};
}
/**
* Only fields that are for properties (ie 1:1 associations)
*/
public final static Predicate<ObjectAssociation> PROPERTIES =
org.apache.isis.applib.filter.Filters.asPredicate(Filters.PROPERTIES);
/**
* Only fields that are for reference properties (ie 1:1 associations)
*/
public final static Predicate<ObjectAssociation> REFERENCE_PROPERTIES =
org.apache.isis.applib.filter.Filters.asPredicate(Filters.REFERENCE_PROPERTIES);
/**
* Only fields that are for properties (ie 1:1 associations)
*/
public final static Predicate<ObjectAssociation> WHERE_VISIBLE_IN_COLLECTION_TABLE =
org.apache.isis.applib.filter.Filters.asPredicate(Filters.WHERE_VISIBLE_IN_COLLECTION_TABLE);
/**
* Only fields that are for properties (ie 1:1 associations)
*/
public final static Predicate<ObjectAssociation> WHERE_VISIBLE_IN_STANDALONE_TABLE =
org.apache.isis.applib.filter.Filters.asPredicate(Filters.WHERE_VISIBLE_IN_STANDALONE_TABLE);
/**
* All fields (that is, excludes out nothing).
*/
public final static Predicate<ObjectAssociation> ALL =
org.apache.isis.applib.filter.Filters.asPredicate(Filters.ALL);
/**
* Only fields that are for collections (ie 1:m associations)
*/
public final static Predicate<ObjectAssociation> COLLECTIONS =
org.apache.isis.applib.filter.Filters.asPredicate(Filters.COLLECTIONS);
/**
* Only properties that are visible statically, ie have not been
* unconditionally hidden at compile time.
*/
public static final Predicate<ObjectAssociation> VISIBLE_AT_LEAST_SOMETIMES =
org.apache.isis.applib.filter.Filters.asPredicate(Filters.VISIBLE_AT_LEAST_SOMETIMES);
public static final Predicate<ObjectAssociation> staticallyVisible(final Where context) {
return org.apache.isis.applib.filter.Filters.asPredicate(Filters.staticallyVisible(context));
}
public static final Predicate<ObjectAssociation> dynamicallyVisible(
final ObjectAdapter target,
final InteractionInitiatedBy interactionInitiatedBy,
final Where where) {
return org.apache.isis.applib.filter.Filters.asPredicate(Filters.dynamicallyVisible(target,
interactionInitiatedBy, where
));
}
public static final Predicate<ObjectAssociation> enabled(
final ObjectAdapter adapter,
final InteractionInitiatedBy interactionInitiatedBy,
final Where where) {
return org.apache.isis.applib.filter.Filters.asPredicate(Filters.enabled(adapter,
interactionInitiatedBy, where
));
}
}
// //////////////////////////////////////////////////////
// Filters
// //////////////////////////////////////////////////////
public static class Filters {
private Filters() {
}
/**
* Filters only fields that are for properties (ie 1:1 associations)
*
* @deprecated -use {@link com.google.common.base.Predicate equivalent}
*/
@Deprecated
public final static Filter<ObjectAssociation> PROPERTIES = new Filter<ObjectAssociation>() {
@Override
public boolean accept(final ObjectAssociation association) {
return association.isOneToOneAssociation();
}
};
/**
* Filters only fields that are for reference properties (ie 1:1 associations)
*
* @deprecated -use {@link com.google.common.base.Predicate equivalent}
*/
@Deprecated
public final static Filter<ObjectAssociation> REFERENCE_PROPERTIES = new Filter<ObjectAssociation>() {
@Override
public boolean accept(final ObjectAssociation association) {
return association.isOneToOneAssociation() &&
!association.getSpecification().containsDoOpFacet(ValueFacet.class);
}
};
/**
* Filters only fields that are for properties (ie 1:1 associations)
*
* @deprecated -use {@link com.google.common.base.Predicate equivalent}
*/
@Deprecated
public final static Filter<ObjectAssociation> WHERE_VISIBLE_IN_COLLECTION_TABLE = new Filter<ObjectAssociation>() {
@Override
public boolean accept(final ObjectAssociation association) {
final HiddenFacet hiddenFacet = association.getFacet(HiddenFacet.class);
return hiddenFacet == null || !hiddenFacet.where().inParentedTable();
}
};
/**
* Filters only fields that are for properties (ie 1:1 associations)
*
* @deprecated -use {@link com.google.common.base.Predicate equivalent}
*/
@Deprecated
public final static Filter<ObjectAssociation> WHERE_VISIBLE_IN_STANDALONE_TABLE = new Filter<ObjectAssociation>() {
@Override
public boolean accept(final ObjectAssociation association) {
final HiddenFacet hiddenFacet = association.getFacet(HiddenFacet.class);
return hiddenFacet == null || !hiddenFacet.where().inStandaloneTable();
}
};
/**
* Returns all fields (that is, filters out nothing).
*
* @deprecated -use {@link com.google.common.base.Predicate equivalent}
*/
@Deprecated
public final static Filter<ObjectAssociation> ALL = new Filter<ObjectAssociation>() {
@Override
public boolean accept(final ObjectAssociation property) {
return true;
}
};
/**
* Filters only fields that are for collections (ie 1:m associations)
*
* @deprecated -use {@link com.google.common.base.Predicate equivalent}
*/
@Deprecated
public final static Filter<ObjectAssociation> COLLECTIONS = new Filter<ObjectAssociation>() {
@Override
public boolean accept(final ObjectAssociation property) {
return property.isOneToManyAssociation();
}
};
/**
* Filters only properties that are visible statically, ie have not been
* unconditionally hidden at compile time.
*
* @deprecated -use {@link com.google.common.base.Predicate equivalent}
*/
@Deprecated
public static final Filter<ObjectAssociation> VISIBLE_AT_LEAST_SOMETIMES = new Filter<ObjectAssociation>() {
@Override
public boolean accept(final ObjectAssociation property) {
final HiddenFacet hiddenFacet = property.getFacet(HiddenFacet.class);
return hiddenFacet == null || hiddenFacet.when() != When.ALWAYS || hiddenFacet.where() != Where.ANYWHERE;
}
};
/**
* @deprecated -use {@link com.google.common.base.Predicate equivalent}
*/
@Deprecated
public static final Filter<ObjectAssociation> staticallyVisible(final Where context) {
return new Filter<ObjectAssociation>() {
@Override
public boolean accept(final ObjectAssociation association) {
final List<Facet> facets = association.getFacets(new Filter<Facet>() {
@Override public boolean accept(final Facet facet) {
return facet instanceof WhenAndWhereValueFacet && facet instanceof HiddenFacet;
}
});
for (Facet facet : facets) {
final WhenAndWhereValueFacet wawF = (WhenAndWhereValueFacet) facet;
if (wawF.where().includes(context) && wawF.when() == When.ALWAYS) {
return false;
}
}
return true;
}
};
}
/**
* @deprecated -use {@link com.google.common.base.Predicate equivalent}
*/
@Deprecated
public static Filter<ObjectAssociation> dynamicallyVisible(
final ObjectAdapter target,
final InteractionInitiatedBy interactionInitiatedBy,
final Where where) {
return new Filter<ObjectAssociation>() {
@Override
public boolean accept(final ObjectAssociation objectAssociation) {
final Consent visible = objectAssociation.isVisible(target, interactionInitiatedBy, where);
return visible.isAllowed();
}
};
}
/**
* @deprecated -use {@link com.google.common.base.Predicate equivalent}
*/
@Deprecated
public static Filter<ObjectAssociation> enabled(
final ObjectAdapter adapter,
final InteractionInitiatedBy interactionInitiatedBy,
final Where where) {
return new Filter<ObjectAssociation>() {
@Override
public boolean accept(final ObjectAssociation objectAssociation) {
final Consent usable = objectAssociation.isUsable(adapter, interactionInitiatedBy, where);
return usable.isAllowed();
}
};
}
}
// //////////////////////////////////////////////////////
// Comparators
// //////////////////////////////////////////////////////
public static class Comparators {
/**
* Use {@link ObjectMember.Comparators#byMemberOrderSequence()} instead.
*/
@Deprecated
public static Comparator<ObjectAssociation> byMemberOrderSequence() {
return new Comparator<ObjectAssociation>() {
private final DeweyOrderComparator deweyOrderComparator = new DeweyOrderComparator();
@Override
public int compare(final ObjectAssociation o1, final ObjectAssociation o2) {
final MemberOrderFacet o1Facet = o1.getFacet(MemberOrderFacet.class);
final MemberOrderFacet o2Facet = o2.getFacet(MemberOrderFacet.class);
return o1Facet == null? +1:
o2Facet == null? -1:
deweyOrderComparator.compare(o1Facet.sequence(), o2Facet.sequence());
}
};
}
}
// //////////////////////////////////////////////////////
// Util
// //////////////////////////////////////////////////////
public static class Util {
private Util(){}
public static Map<String, List<ObjectAssociation>> groupByMemberOrderName(
final List<ObjectAssociation> associations) {
Map<String, List<ObjectAssociation>> associationsByGroup = Maps.newHashMap();
for(ObjectAssociation association: associations) {
addAssociationIntoGroup(associationsByGroup, association);
}
for (Map.Entry<String, List<ObjectAssociation>> objectAssociations : associationsByGroup.entrySet()) {
Collections.sort(objectAssociations.getValue(), new MemberOrderComparator(true));
}
return associationsByGroup;
}
private static void addAssociationIntoGroup(
final Map<String, List<ObjectAssociation>> associationsByGroup,
final ObjectAssociation association) {
final MemberOrderFacet memberOrderFacet = association.getFacet(MemberOrderFacet.class);
if(memberOrderFacet != null) {
final String untranslatedName = memberOrderFacet.untranslatedName();
if(!Strings.isNullOrEmpty(untranslatedName)) {
getFrom(associationsByGroup, untranslatedName).add(association);
return;
}
}
getFrom(associationsByGroup, MemberGroupLayoutFacet.DEFAULT_GROUP).add(association);
}
private static List<ObjectAssociation> getFrom(Map<String, List<ObjectAssociation>> associationsByGroup, final String groupName) {
List<ObjectAssociation> list = associationsByGroup.get(groupName);
if(list == null) {
list = Lists.newArrayList();
associationsByGroup.put(groupName, list);
}
return list;
}
}
}