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 and
059         * nameval contains pairs of "key" : value instances
060         * 
061         * @param nameval
062         */
063        public Context(Object... nameval) {
064                for (int i = 0; i < nameval.length; i += 2) {
065                        this.put(nameval[i].toString(), nameval[i + 1]);
066                }
067        }
068
069        /**
070         * Construct an empty {@link Context}. The Context can optionally be
071         * configured to either return <code>null</code> from
072         * {@link #getTyped(String)} or throw a {@link RuntimeException} if there is
073         * a casting issue or the object doesn't exist.
074         *
075         * @param failfast
076         *            forces the getTyped to throw a runtime exception if set and
077         *            the object does not exist or is not the correct type
078         */
079        public Context(boolean failfast) {
080                this.failfast = failfast;
081        }
082
083        /**
084         * Get the object from the context with the given key and coerce the type.
085         *
086         * @param key
087         *            the key
088         * @return the object with the given key coerced to the specific return type
089         */
090        public <T> T getTyped(String key) {
091                final Object retUntyped = this.get(key);
092
093                if (retUntyped == null) {
094                        if (failfast)
095                                throw new RuntimeException(new NoSuchElementException("Object not found"));
096                        return null;
097                }
098
099                try {
100                        @SuppressWarnings("unchecked")
101                        final T ret = (T) retUntyped;
102
103                        return ret;
104                } catch (final Throwable t) {
105                        if (failfast)
106                                throw new RuntimeException(t);
107
108                        return null;
109                }
110        }
111
112        @Override
113        public Context clone() {
114                final Context c = new Context();
115                for (final java.util.Map.Entry<String, Object> es : this.entrySet()) {
116                        c.put(es.getKey(), es.getValue());
117                }
118                return c;
119        }
120
121        /**
122         * Combine this {@link Context} with another context by modifying any shared
123         * keys of both contexts to be prefixed with the given prefixes and then
124         * copying all the data from the given {@link Context} into this one.
125         * <p>
126         * If both prefixes are the same then the data being copied from the other
127         * context will have precedence. The prefixes can be <code>null</code>.
128         *
129         * @param that
130         *            the context to combine with this
131         * @param thisprefix
132         *            the prefix for keys in this context
133         * @param thatprefix
134         *            the prefix for keys in the other context
135         * @return combined context
136         */
137        public Context combine(Context that, String thisprefix, String thatprefix) {
138                final Context combined = new Context();
139
140                final HashSet<String> sharedKeys = new HashSet<String>(this.keySet());
141                sharedKeys.retainAll(that.keySet());
142
143                final HashSet<String> thiskeys = new HashSet<String>(this.keySet());
144                thiskeys.removeAll(sharedKeys);
145
146                final HashSet<String> thatkeys = new HashSet<String>(that.keySet());
147                thatkeys.removeAll(sharedKeys);
148
149                if (thisprefix == null)
150                        thisprefix = "";
151                if (thatprefix == null)
152                        thatprefix = "";
153
154                // Add the prefix
155                for (final String key : sharedKeys) {
156                        combined.put(thisprefix + key, this.get(key));
157                        combined.put(thatprefix + key, that.get(key));
158                }
159
160                for (final String key : thatkeys) {
161                        combined.put(key, that.get(key));
162                }
163
164                for (final String key : thiskeys) {
165                        combined.put(key, this.get(key));
166                }
167
168                return combined;
169        }
170}