001/**
002 * Copyright (c) 2011, The University of Southampton and the individual contributors.
003 * All rights reserved.
004 *
005 * Redistribution and use in source and binary forms, with or without modification,
006 * are permitted provided that the following conditions are met:
007 *
008 *   *  Redistributions of source code must retain the above copyright notice,
009 *      this list of conditions and the following disclaimer.
010 *
011 *   *  Redistributions in binary form must reproduce the above copyright notice,
012 *      this list of conditions and the following disclaimer in the documentation
013 *      and/or other materials provided with the distribution.
014 *
015 *   *  Neither the name of the University of Southampton nor the names of its
016 *      contributors may be used to endorse or promote products derived from this
017 *      software without specific prior written permission.
018 *
019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 */
030package org.openimaj.util.data;
031
032import java.util.HashMap;
033import java.util.HashSet;
034import java.util.Map;
035import java.util.NoSuchElementException;
036
037/**
038 * A Context is a {@link Map} which can give typed elements and can fail
039 * gracefully when elements don't exist.
040 * 
041 * @author Sina Samangooei (ss@ecs.soton.ac.uk)
042 */
043public class Context extends HashMap<String, Object> implements Cloneable {
044        private static final long serialVersionUID = 1888665727867672296L;
045
046        private boolean failfast = false;
047
048        /**
049         * Default constructor to make an empty {@link Context}. The Context is
050         * configured to return <code>null</code> from {@link #getTyped(String)} if
051         * there is a casting issue or the object doesn't exist.
052         */
053        public Context() {
054
055        }
056        
057        /**
058         * A conveniance function where nameval.length % 2 is assumed to  == 0 
059         * and nameval contains pairs of "key" : value instances
060         * @param nameval
061         */
062        public Context(Object ... nameval){
063                for (int i = 0; i < nameval.length; i+=2) {
064                        this.put(nameval[i].toString(), nameval[i+1]);
065                }
066        }
067
068        /**
069         * Construct an empty {@link Context}. The Context can optionally be
070         * configured to either return <code>null</code> from
071         * {@link #getTyped(String)} or throw a {@link RuntimeException} if there is
072         * a casting issue or the object doesn't exist.
073         * 
074         * @param failfast
075         *            forces the getTyped to throw a runtime exception if set and
076         *            the object does not exist or is not the correct type
077         */
078        public Context(boolean failfast) {
079                this.failfast = failfast;
080        }
081
082        /**
083         * Get the object from the context with the given key and coerce the type.
084         * 
085         * @param key
086         *            the key
087         * @return the object with the given key coerced to the specific return type
088         */
089        public <T> T getTyped(String key) {
090                final Object retUntyped = this.get(key);
091
092                if (retUntyped == null) {
093                        if (failfast)
094                                throw new RuntimeException(new NoSuchElementException("Object not found"));
095                        return null;
096                }
097
098                try {
099                        @SuppressWarnings("unchecked")
100                        final T ret = (T) retUntyped;
101
102                        return ret;
103                } catch (final Throwable t) {
104                        if (failfast)
105                                throw new RuntimeException(t);
106
107                        return null;
108                }
109        }
110
111        @Override
112        public Context clone() {
113                final Context c = new Context();
114                for (final java.util.Map.Entry<String, Object> es : this.entrySet()) {
115                        c.put(es.getKey(), es.getValue());
116                }
117                return c;
118        }
119
120        /**
121         * Combine this {@link Context} with another context by modifying any shared
122         * keys of both contexts to be prefixed with the given prefixes and then
123         * copying all the data from the given {@link Context} into this one.
124         * <p>
125         * If both prefixes are the same then the data being copied from the other
126         * context will have precedence. The prefixes can be <code>null</code>.
127         * 
128         * @param that
129         *            the context to combine with this
130         * @param thisprefix
131         *            the prefix for keys in this context
132         * @param thatprefix
133         *            the prefix for keys in the other context
134         * @return combined context
135         */
136        public Context combine(Context that, String thisprefix, String thatprefix) {
137                final Context combined = new Context();
138
139                final HashSet<String> sharedKeys = new HashSet<String>(this.keySet());
140                sharedKeys.retainAll(that.keySet());
141
142                final HashSet<String> thiskeys = new HashSet<String>(this.keySet());
143                thiskeys.removeAll(sharedKeys);
144
145                final HashSet<String> thatkeys = new HashSet<String>(that.keySet());
146                thatkeys.removeAll(sharedKeys);
147
148                if (thisprefix == null)
149                        thisprefix = "";
150                if (thatprefix == null)
151                        thatprefix = "";
152
153                // Add the prefix
154                for (final String key : sharedKeys) {
155                        combined.put(thisprefix + key, this.get(key));
156                        combined.put(thatprefix + key, that.get(key));
157                }
158
159                for (final String key : thatkeys) {
160                        combined.put(key, that.get(key));
161                }
162
163                for (final String key : thiskeys) {
164                        combined.put(key, this.get(key));
165                }
166
167                return combined;
168        }
169}