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:[email protected]">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}