001/**
002 * Copyright 2005-2016 The Kuali Foundation
003 *
004 * Licensed under the Educational Community 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.opensource.org/licenses/ecl2.php
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 */
016package org.kuali.rice.krms.api.engine;
017
018import java.io.Serializable;
019import java.util.Collections;
020import java.util.Comparator;
021import java.util.Iterator;
022import java.util.Map;
023import java.util.Map.Entry;
024import java.util.TreeMap;
025
026import org.springframework.util.CollectionUtils;
027
028/**
029 * Identifies a (hopefully) resolvable {@link Term}.  For resolution in the {@link TermResolutionEngine}, The
030 * appropriate {@link TermResolver} will be selected by matching the name and parameters of the {@link Term} with
031 * the output and parameter names of the {@link TermResolver}. 
032 *
033 * @author Kuali Rice Team (rice.collab@kuali.org)
034 */
035public final class Term implements Comparable<Term> {
036
037        private final String name;
038        
039        private final Map<String, String> parameters;
040        
041        private static final TreeMapComparator<String,String> treeMapComparator = new TreeMapComparator<String, String>();
042
043    /**
044     * Constructor
045     * @param name of the term
046     */
047        public Term(String name) {
048                this(name, null);
049        }       
050        
051        /**
052         * This constructs a Term, which is a named piece of data that is usually obtainable
053         * through the {@link TermResolutionEngine}
054         *
055     * @param name the term name
056     * @param parameters an optional map of properties that may be used to allow a single TermResolver to resolve multiple Terms
057     */
058        public Term(String name, Map<String, String> parameters) {
059                this.name = name;
060                if (parameters == null) {
061                        this.parameters = Collections.emptyMap();
062                } else {
063                        // using TreeMap for ordered iteration since we're comparable
064                        this.parameters = Collections.unmodifiableMap(new TreeMap<String, String>(parameters));
065                }
066        }
067
068    /**
069     * Return the name of the term
070     * @return name of the term
071     */
072        public String getName() { return this.name; }
073        public Map<String, String> getProperties() { return parameters; }
074
075        /* (non-Javadoc)
076         * @see java.lang.Object#hashCode()
077         */
078        @Override
079        public int hashCode() {
080                final int prime = 31;
081                int result = 1;
082                result = prime * result + ((name == null) ? 0 : name.hashCode());
083                result = prime * result + ((parameters == null) ? 0 : parameters.hashCode());
084                return result;
085        }
086
087        /* (non-Javadoc)
088         * @see java.lang.Object#equals(java.lang.Object)
089         */
090        @Override
091        public boolean equals(Object obj) {
092                if (this == obj)
093                        return true;
094                if (obj == null)
095                        return false;
096                if (getClass() != obj.getClass())
097                        return false;
098                Term other = (Term) obj;
099                return this.compareTo(other) == 0;
100        }
101
102        @Override
103        public int compareTo(Term o) {
104                if (o == null) return 1;
105                if (this == o) return 0;
106                return (treeMapComparator.compare(this.parameters, o.parameters));
107        }
108        
109        /**
110     * Return an unmodifiable Map of parameters specified on this Term.
111         * @return an unmodifiable Map of parameters specified on this Term.  Guaranteed non-null.
112         */
113        public Map<String, String> getParameters() {
114                return Collections.unmodifiableMap(parameters);
115        }
116
117        @Override
118        public String toString() {
119                // TODO make this pretty
120                StringBuilder sb = new StringBuilder();
121                if (parameters != null) for (Entry<String,String> parameter : parameters.entrySet()) {
122                        sb.append(", ");
123                        sb.append(parameter.getKey());
124                        sb.append("=");
125                        sb.append(parameter.getValue());
126                }
127                return getClass().getSimpleName()+"(["+ name + "]" +  sb.toString() + ")";
128        }
129
130        @SuppressWarnings("rawtypes")
131        private static class TreeMapComparator<T extends Comparable, V extends Comparable> implements Comparator<Map<T,V>>, Serializable {
132                
133                private static final long serialVersionUID = 1L;
134
135                /**
136                 * This overridden method compares two {@link TreeMap}s whose keys and elements are both {@link Comparable}
137                 * 
138                 * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
139                 */
140                @SuppressWarnings("unchecked")
141        @Override
142                public int compare(Map<T,V> o1, Map<T,V> o2) {
143                        if (CollectionUtils.isEmpty(o1)) {
144                                if (CollectionUtils.isEmpty(o2)) return 0;
145                                return -1;
146                        } else if (CollectionUtils.isEmpty(o2)) {
147                                return 1;
148                        }
149                        
150                        // neither one is empty.  Iterate through both.
151
152                        Iterator<Entry<T,V>> o1Iter = o1.entrySet().iterator();
153                        Iterator<Entry<T,V>> o2Iter = o2.entrySet().iterator();
154                        
155                        while (o1Iter.hasNext() && o2Iter.hasNext()) {
156                                Entry<T,V> o1Elem = o1Iter.next(); 
157                                Entry<T,V> o2Elem = o2Iter.next();
158                                if (o1Elem == null) {
159                                        if (o2Elem == null) continue;
160                                        return -1;
161                                } 
162                                
163                                T o1ElemKey = o1Elem.getKey();
164                                T o2ElemKey = o2Elem.getKey();
165                                if (o1ElemKey == null) {
166                                        if (o2ElemKey != null) return -1;
167                                        // if they're both null, fall through
168                                } else {
169                                        int elemKeyCompare = o1ElemKey.compareTo(o2ElemKey);
170                                        if (elemKeyCompare != 0) return elemKeyCompare;
171                                }
172                                
173                                V o1ElemValue = o1Elem.getValue();
174                                V o2ElemValue = o2Elem.getValue();
175                                if (o1ElemValue == null) {
176                                        if (o2ElemValue != null) return -1;
177                                        // if they're both null, fall through
178                                } else {
179                                        int elemValueCompare = o1ElemValue.compareTo(o2ElemValue);
180                                        if (elemValueCompare != 0) return elemValueCompare;
181                                }
182                        }
183                        
184                        if (o1Iter.hasNext()) return 1;
185                        if (o2Iter.hasNext()) return -1;
186                        return 0;
187                }
188
189        }
190
191        
192}