001/*
002 * (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a>
018 *
019 * $Id$
020 */
021
022package org.nuxeo.ecm.core.api;
023
024import java.text.Collator;
025import java.util.LinkedHashMap;
026import java.util.Map;
027import java.util.Map.Entry;
028
029/**
030 * DocumentModel comparator. Uses ordering independent of case or accent. If two values are integers/longs, numbering
031 * comparison is used.
032 *
033 * @author Florent Guillaume
034 * @author Anahide Tchertchian
035 */
036public class DocumentModelComparator implements Sorter {
037
038    private static final long serialVersionUID = 1L;
039
040    public static final String ORDER_ASC = "asc";
041
042    static final Collator collator = Collator.getInstance();
043
044    static {
045        collator.setStrength(Collator.PRIMARY); // case+accent independent
046    }
047
048    final String schemaName;
049
050    final Map<String, String> orderBy;
051
052    /**
053     * Constructor using a schema and a map of field names to compare on.
054     *
055     * @param schemaName the schema name
056     * @param orderBy map using property names as keys, and "asc" or "desc" as values. Should be a {@link LinkedHashMap}
057     *            if order of criteria matters.
058     */
059    public DocumentModelComparator(String schemaName, Map<String, String> orderBy) {
060        this.schemaName = schemaName;
061        this.orderBy = orderBy;
062    }
063
064    /**
065     * Constructor using a map of property names to compare on.
066     *
067     * @param orderBy map using property names as keys, and "asc" or "desc" as values. Should be a {@link LinkedHashMap}
068     *            if order of criteria matters.
069     */
070    public DocumentModelComparator(Map<String, String> orderBy) {
071        this(null, orderBy);
072    }
073
074    protected int compare(Object v1, Object v2, boolean asc) {
075        if (v1 == null && v2 == null) {
076            return 0;
077        } else if (v1 == null) {
078            return asc ? -1 : 1;
079        } else if (v2 == null) {
080            return asc ? 1 : -1;
081        }
082        final int cmp;
083        if (v1 instanceof Long && v2 instanceof Long) {
084            cmp = ((Long) v1).compareTo((Long) v2);
085        } else if (v1 instanceof Integer && v2 instanceof Integer) {
086            cmp = ((Integer) v1).compareTo((Integer) v2);
087        } else {
088            cmp = collator.compare(v1.toString(), v2.toString());
089        }
090        return asc ? cmp : -cmp;
091    }
092
093    @Override
094    public int compare(DocumentModel doc1, DocumentModel doc2) {
095        if (doc1 == null && doc2 == null) {
096            return 0;
097        } else if (doc1 == null) {
098            return -1;
099        } else if (doc2 == null) {
100            return 1;
101        }
102
103        int cmp = 0;
104        if (schemaName != null) {
105            DataModel d1 = doc1.getDataModel(schemaName);
106            DataModel d2 = doc2.getDataModel(schemaName);
107            for (Entry<String, String> e : orderBy.entrySet()) {
108                final String fieldName = e.getKey();
109                final boolean asc = ORDER_ASC.equals(e.getValue());
110                Object v1 = d1.getData(fieldName);
111                Object v2 = d2.getData(fieldName);
112                cmp = compare(v1, v2, asc);
113                if (cmp != 0) {
114                    break;
115                }
116            }
117        } else {
118            for (Entry<String, String> e : orderBy.entrySet()) {
119                final String propertyName = e.getKey();
120                final boolean asc = ORDER_ASC.equals(e.getValue());
121                Object v1 = null;
122                try {
123                    v1 = doc1.getPropertyValue(propertyName);
124                } catch (PropertyException pe) {
125                    v1 = null;
126                }
127                Object v2 = null;
128                try {
129                    v2 = doc2.getPropertyValue(propertyName);
130                } catch (PropertyException pe) {
131                    v2 = null;
132                }
133                cmp = compare(v1, v2, asc);
134                if (cmp != 0) {
135                    break;
136                }
137            }
138        }
139        if (cmp == 0) {
140            // everything being equal, provide consistent ordering
141            if (doc1.hashCode() == doc2.hashCode()) {
142                cmp = 0;
143            } else if (doc1.hashCode() < doc2.hashCode()) {
144                cmp = -1;
145            } else {
146                cmp = 1;
147            }
148        }
149        return cmp;
150    }
151
152}