/* * Copyright 2012 Shared Learning Collaborative, LLC * * Licensed 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.slc.sli.dashboard.entity.util; import java.lang.reflect.Constructor; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.Map; import java.util.Set; /** * Comparator for elements in GenericEntity by a specified Map<String,Integer> Priority or the * Object's Comparable interface. */ public class GenericEntityComparator implements Comparator<Map<String, Object>> { /** field name being used for comparisons */ private String fieldName = ""; /** priority list optionally used to effect comparisons */ private Map<?, Integer> priorityList = null; private Class<?> castingClass = String.class; /** * Compare by given field name of JSON. Use specified priority list for comparisons. * * @param fieldName * field name of JSON for comparison * @param priorityList * String: name of type, Integer priority (1 is the highest priority) */ public GenericEntityComparator(String fieldName, Map<?, Integer> priorityList) { this.fieldName = fieldName; if (priorityList == null) { this.priorityList = Collections.emptyMap(); } else { this.priorityList = priorityList; // determine the class of key. if (!this.priorityList.isEmpty()) { Set<?> keys = this.priorityList.keySet(); if (keys != null && !keys.isEmpty()) { Iterator<?> i = keys.iterator(); while (i.hasNext()) { Object o = i.next(); // if an object is null, find next available. if (o == null) { continue; } //just need to find the class once. this.castingClass = i.next().getClass(); break; } } } } } /** * Compare by given field name of JSON. This method does not use a priorityList for comparisons * but rather leverages the Comparable interface being implemented by the specified field's * underlying object. Note that String, Integer, Float etc. implement Comparable and behave as * expected (e.g. Strings compare alphabetically, case sensitive). If the objects in the * specified field do not implement Comparable, using this constructor will result in no actual * comparisons being made - all objects will be treated as equal. * * @param fieldName * field name of JSON for comparison * @param targetClass * class which implemented Comparable. JSON value will be re-instantiate with this * class in order to compare two objects * */ public GenericEntityComparator(String fieldName, Class<? extends Comparable<?>> targetClass) { this.fieldName = fieldName; this.priorityList = null; this.castingClass = targetClass; } @Override public int compare(Map<String, Object> o1, Map<String, Object> o2) { // get the objects for the field being compared Object o1Type = o1.get(this.fieldName); Object o2Type = o2.get(this.fieldName); if (priorityList != null) { // compare using the defined priority list // Note that this does not behave "as expected"; i.e. if the underlying objects are // Strings, they will not be compared alphabetically. Rather, the ones with values // defined in the priority list will take precedence over others with no such values // (which are all equal as far as this Comparator is concerned) // temporary assigning priority. Make it lowest possible. int o1Priority = Integer.MAX_VALUE; int o2Priority = Integer.MAX_VALUE; // find true priority if (o1Type != null) { // if targetClass is other than String class, // then re-instantiate to an appropriate object. if (String.class.equals(this.castingClass)) { // if o1Type is not String object, then get String if (!o1Type.getClass().equals(this.castingClass)) { o1Type = o1Type.toString(); } } else { try { Constructor<?> constructor = this.castingClass.getConstructor(String.class); o1Type = constructor.newInstance(o1Type.toString()); } catch (Exception e) { o1Priority = Integer.MAX_VALUE; } } if (this.priorityList.containsKey(o1Type)) { o1Priority = this.priorityList.get(o1Type); } } if (o2Type != null) { // if targetClass is other than String class, // then re-instantiate to an appropriate object. if (String.class.equals(this.castingClass)) { // if o12ype is not String object, then get String if (!o2Type.getClass().equals(this.castingClass)) { o2Type = o2Type.toString(); } } else { try { Constructor<?> constructor = this.castingClass.getConstructor(String.class); o2Type = constructor.newInstance(o2Type.toString()); } catch (Exception e) { o2Priority = Integer.MAX_VALUE; } } if (this.priorityList.containsKey(o2Type)) { o2Priority = this.priorityList.get(o2Type); } } // if a field's value is present in the priority list, it has precedence // otherwise, all field values not in the priority list have equal precedence return o1Priority == o2Priority ? 0 : (o1Priority < o2Priority ? -1 : 1); } else { int result = 0; // compare using the underlying object's Comparable interface // This comparison behaves "as expected", e.g. smaller Integers come before bigger // Integers, Strings compare alphabetically (case sensitive) etc. // Note that, currently, if the underlying object does not implement Comparable, it // cannot be compared by this class unless you use the priorityList option try { Comparable o1TypeComparable = null; Comparable o2TypeComparable = null; // if castingClass is String, then just simply cast if (String.class.equals(this.castingClass)) { o1TypeComparable = (String) o1Type; o2TypeComparable = (String) o2Type; result = o1TypeComparable.compareTo(o2TypeComparable); } else { try { // throwing exception is very expensive. // so, check an object is null or not before calling toString method. // if an object is null, then the object has the lowest priority if (o1Type != null && o2Type != null) { // Let's assume that all Comparable implemented class takes one String // object parameter in its constructor. Constructor<?> constructor = this.castingClass.getConstructor(String.class); o1TypeComparable = (Comparable) constructor.newInstance(o1Type.toString()); o2TypeComparable = (Comparable) constructor.newInstance(o2Type.toString()); } else if (o1Type != null && o2Type == null) { result = -1; } else if (o1Type == null && o2Type != null) { result = 1; } else if (o1Type == null && o2Type == null) { result = 0; } } catch (Exception e) { result = 0; } } } catch (ClassCastException cce) { // does not implement Comparable, cannot be compared result = 0; } return result; } } }