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