001    /**
002     * Copyright 2005-2013 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     */
016    package org.kuali.rice.krms.api.engine;
017    
018    import java.io.Serializable;
019    import java.util.Collections;
020    import java.util.Comparator;
021    import java.util.Iterator;
022    import java.util.Map;
023    import java.util.Map.Entry;
024    import java.util.TreeMap;
025    
026    import 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     */
035    public 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    }