/*
* 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.layout;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.StringTokenizer;
import java.util.TreeSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.apache.isis.core.metamodel.facetapi.FacetHolder;
import org.apache.isis.core.metamodel.facetapi.IdentifiedHolder;
import org.apache.isis.core.metamodel.facets.FacetedMethod;
import org.apache.isis.core.metamodel.facets.members.order.MemberOrderFacet;
import org.apache.isis.core.metamodel.layout.memberorderfacet.MemberIdentifierComparator;
import org.apache.isis.core.metamodel.layout.memberorderfacet.MemberOrderComparator;
/**
* Represents a nested hierarchy of ordered members.
*
* <p>
* At each level the elements are either {@link FacetedMethod}s or they are
* instances of {@link OrderSet} represent a group of {@link FacetedMethod}s
* that have a {@link MemberOrderFacet} of the same name.
*
* <p>
* With no name, (ie <tt>name=""</tt> is the default), at the top level
*
* <pre>
* MemberOrder(sequence="1")
* MemberOrder(sequence="1.1")
* MemberOrder(sequence="1.2")
* MemberOrder(sequence="1.2.1")
* MemberOrder(sequence="1.3")
* </pre>
*
* <p>
* With names, creates a hierarchy.
*
* <pre>
* MemberOrder(sequence="1.1") // no parent
* MemberOrder(sequence="1.2.1")
* MemberOrder(sequence="1.3")
* MemberOrder(name="abc", sequence="1") // group is abc, parent is ""
* MemberOrder(name="abc", sequence="1.2")
* MemberOrder(name="abc,def", sequence="1") // group is def, parent is abc
* MemberOrder(name="abc,def", sequence="1.2")
* </pre>
*
*/
public class DeweyOrderSet implements Comparable<DeweyOrderSet>, Iterable<Object> {
public static DeweyOrderSet createOrderSet(final List<? extends IdentifiedHolder> identifiedHolders) {
final SortedMap<String, SortedSet<IdentifiedHolder>> sortedMembersByGroup = Maps.newTreeMap();
final SortedSet<IdentifiedHolder> nonAnnotatedGroup = Sets.newTreeSet(new MemberIdentifierComparator());
// spin over all the members and put them into a Map of SortedSets
// any non-annotated members go into additional nonAnnotatedGroup set.
for (final IdentifiedHolder identifiedHolder : identifiedHolders) {
final MemberOrderFacet memberOrder = identifiedHolder.getFacet(MemberOrderFacet.class);
if (memberOrder == null) {
nonAnnotatedGroup.add(identifiedHolder);
continue;
}
final SortedSet<IdentifiedHolder> sortedMembersForGroup = getSortedSet(sortedMembersByGroup, memberOrder.name());
sortedMembersForGroup.add(identifiedHolder);
}
// add the non-annotated group to the first "" group.
final SortedSet<IdentifiedHolder> defaultSet = getSortedSet(sortedMembersByGroup, "");
defaultSet.addAll(nonAnnotatedGroup);
// create OrderSets, wiring up parents and children.
// since sortedMembersByGroup is a SortedMap, the
// iteration will be in alphabetical order (ie parent groups before
// their children).
final Set<String> groupNames = sortedMembersByGroup.keySet();
final SortedMap<String, DeweyOrderSet> orderSetsByGroup = Maps.newTreeMap();
for (final String string : groupNames) {
final String groupName = string;
final DeweyOrderSet deweyOrderSet = new DeweyOrderSet(groupName);
orderSetsByGroup.put(groupName, deweyOrderSet);
ensureParentFor(orderSetsByGroup, deweyOrderSet);
}
// now populate the OrderSets
for (final String groupName : groupNames) {
final DeweyOrderSet deweyOrderSet = orderSetsByGroup.get(groupName);
// REVIEW: something fishy happens here with casting, hence warnings
// left in
final SortedSet sortedMembers = sortedMembersByGroup.get(groupName);
deweyOrderSet.addAll(sortedMembers);
deweyOrderSet.copyOverChildren();
}
return orderSetsByGroup.get("");
}
/**
* Recursively creates parents all the way up to root (<tt>""</tt>), along
* the way associating each child with its parent and adding the child as an
* element of its parent.
*
* @param orderSetsByGroup
* @param deweyOrderSet
*/
private static void ensureParentFor(final SortedMap<String,DeweyOrderSet> orderSetsByGroup, final DeweyOrderSet deweyOrderSet) {
final String parentGroup = deweyOrderSet.getGroupPath();
DeweyOrderSet parentOrderSet = (DeweyOrderSet) orderSetsByGroup.get(parentGroup);
if (parentOrderSet == null) {
parentOrderSet = new DeweyOrderSet(parentGroup);
orderSetsByGroup.put(parentGroup, parentOrderSet);
if (!parentGroup.equals("")) {
ensureParentFor(orderSetsByGroup, deweyOrderSet);
}
}
// check in case at root
if (deweyOrderSet != parentOrderSet) {
deweyOrderSet.setParent(parentOrderSet);
parentOrderSet.addChild(deweyOrderSet);
}
}
/**
* Gets the SortedSet with the specified group from the supplied Map of
* SortedSets.
*
* <p>
* If there is no such SortedSet, creates.
*
* @param sortedMembersByGroup
* @param groupName
* @return
*/
private static SortedSet<IdentifiedHolder> getSortedSet(final SortedMap<String, SortedSet<IdentifiedHolder>> sortedMembersByGroup, final String groupName) {
SortedSet<IdentifiedHolder> sortedMembersForGroup = sortedMembersByGroup.get(groupName);
if (sortedMembersForGroup == null) {
sortedMembersForGroup = new TreeSet<IdentifiedHolder>(new MemberOrderComparator(true));
sortedMembersByGroup.put(groupName, sortedMembersForGroup);
}
return sortedMembersForGroup;
}
// /////////////////////////////////////////////////////////////////////////
private final List<Object> elements = Lists.newArrayList();
private final String groupFullName;
private final String groupName;
private final String groupPath;
private DeweyOrderSet parent;
/**
* A staging area until we are ready to add the child sets to the collection
* of elements owned by the superclass.
*/
protected SortedSet<DeweyOrderSet> childOrderSets = new TreeSet<DeweyOrderSet>();
// /////////////////////////////////////////////////////////////////////////
private DeweyOrderSet(final String groupFullName) {
this.groupFullName = groupFullName;
groupName = deriveGroupName(groupFullName);
groupPath = deriveGroupPath(groupFullName);
}
// ///////////////// Group Name etc ////////////////////
/**
* Last component of the comma-separated group name supplied in the
* constructor (analogous to the file name extracted from a fully qualified
* file name).
*
* <p>
* For example, if supplied <tt>abc,def,ghi</tt> in the constructor, then
* this will return <tt>ghi</tt>.
*/
public String getGroupName() {
return groupName;
}
/**
* The group name exactly as it was supplied in the constructor (analogous
* to a fully qualified file name).
*
* <p>
* For example, if supplied <tt>abc,def,ghi</tt> in the constructor, then
* this will return the same string <tt>abc,def,ghi</tt>.
*/
public String getGroupFullName() {
return groupFullName;
}
/**
* Represents the parent groups, derived from the group name supplied in the
* constructor (analogous to the directory portion of a fully qualified file
* name).
*
* <p>
* For example, if supplied <tt>abc,def,ghi</tt> in the constructor, then
* this will return <tt>abc,def</tt>.
*/
public String getGroupPath() {
return groupPath;
}
/**
* Splits name by comma, then title case the last component.
*/
private static String deriveGroupName(final String groupFullName) {
final StringTokenizer tokens = new StringTokenizer(groupFullName, ",", false);
final String[] groupNameComponents = new String[tokens.countTokens()];
for (int i = 0; tokens.hasMoreTokens(); i++) {
groupNameComponents[i] = tokens.nextToken();
}
final String groupSimpleName = groupNameComponents.length > 0 ? groupNameComponents[groupNameComponents.length - 1] : "";
if (groupSimpleName.length() > 1) {
return groupSimpleName.substring(0, 1).toUpperCase() + groupSimpleName.substring(1);
} else {
return groupSimpleName.toUpperCase();
}
}
/**
* Everything up to the last comma, else empty string if none.
*/
private static String deriveGroupPath(final String groupFullName) {
final int lastComma = groupFullName.lastIndexOf(",");
if (lastComma == -1) {
return "";
}
return groupFullName.substring(0, lastComma);
}
// ///////////////////// Parent & Children ///////////////////
protected void setParent(final DeweyOrderSet parent) {
this.parent = parent;
}
public DeweyOrderSet getParent() {
return parent;
}
protected void addChild(final DeweyOrderSet childOrderSet) {
childOrderSets.add(childOrderSet);
}
public List<DeweyOrderSet> children() {
final ArrayList<DeweyOrderSet> list = new ArrayList<DeweyOrderSet>();
list.addAll(childOrderSets);
return list;
}
protected void copyOverChildren() {
addAll(childOrderSets);
}
// ///////////////////// Elements (includes children) ///////////////////
/**
* Returns a copy of the elements, in sequence.
*/
public List<Object> elementList() {
return new ArrayList<Object>(elements);
}
public int size() {
return elements.size();
}
protected void addElement(final Object element) {
elements.add(element);
}
@Override
public Iterator<Object> iterator() {
return elements.iterator();
}
protected void addAll(final SortedSet<?> sortedMembers) {
for (final Object deweyOrderSet : sortedMembers) {
this.addElement(deweyOrderSet);
}
}
// ///////////////////////// reorderChildren //////////////////////
public void reorderChildren(List<String> requiredOrder) {
final LinkedHashMap<String,DeweyOrderSet> orderSets = Maps.newLinkedHashMap();
// remove all OrderSets from elements
// though remembering the order they were encountered
for (Object child : elementList()) {
if(child instanceof DeweyOrderSet) {
final DeweyOrderSet orderSet = (DeweyOrderSet) child;
elements.remove(orderSet);
orderSets.put(orderSet.getGroupName(), orderSet);
}
}
// spin through the requiredOrder and add back in (if found)
for (String group : requiredOrder) {
DeweyOrderSet orderSet = orderSets.get(group);
if(orderSet == null) {
continue;
}
orderSets.remove(group);
elements.add(orderSet);
}
// anything left, add back in the original order
for (String orderSetGroupName : orderSets.keySet()) {
final DeweyOrderSet orderSet = orderSets.get(orderSetGroupName);
elements.add(orderSet);
}
}
// //////////////////////////////////////
// compareTo, equals, hashCode, toString
// //////////////////////////////////////
/**
* Natural ordering is to compare by {@link #getGroupFullName()}.
*/
@Override
public int compareTo(final DeweyOrderSet o) {
if (this.equals(o)) {
return 0;
}
return groupFullName.compareTo(o.groupFullName);
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final DeweyOrderSet other = (DeweyOrderSet) obj;
if (groupFullName == null) {
if (other.groupFullName != null) {
return false;
}
} else if (!groupFullName.equals(other.groupFullName)) {
return false;
}
return true;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((groupFullName == null) ? 0 : groupFullName.hashCode());
return result;
}
/**
* Format is: <tt>abc,def:XXel/YYm/ZZch</tt>
* <p>
* where <tt>abc,def</tt> is group name, <tt>XX</tt> is number of elements,
* <tt>YY is number of members, and
* <tt>ZZ</tt> is number of child order sets.
*/
@Override
public String toString() {
return getGroupFullName() + ":" + size() + "el/" + (size() - childOrderSets.size()) + "m/" + childOrderSets.size() + "ch";
}
}