001/*
002 *                    BioJava development code
003 *
004 * This code may be freely distributed and modified under the
005 * terms of the GNU Lesser General Public Licence.  This should
006 * be distributed with the code.  If you do not have a copy,
007 * see:
008 *
009 *      http://www.gnu.org/copyleft/lesser.html
010 *
011 * Copyright for this code is held jointly by the individual
012 * authors.  These should be listed in @author doc comments.
013 *
014 * For more information on the BioJava project and its aims,
015 * or to join the biojava-l mailing list, visit the home page
016 * at:
017 *
018 *      http://www.biojava.org/
019 *
020 */
021
022package org.biojava.nbio.ontology.utils;
023
024import java.io.Serializable;
025import java.util.*;
026
027
028/**
029 * A utility class to ease the problem of implementing an Annotation to that of
030 * providing an apropreate implementation of Map. Where possible implementations
031 *
032 * This class is only intended as a way to implement
033 * Annotation. If you are not trying to do that, then don't read on. If you
034 * are reading the documentation for an Annotation implementation that extends
035 * this, then don't read on. There is nothing to see here.
036 *
037 * If you are still reading this, then you must be trying to
038 * implement Annotation. To do that, extend this class and implement
039 * <code>getProperties()</code> and <code>propertiesAllocated()</code>.
040 * Where possible implementations should be backed with a
041 * <code>LinkedHashMap</code> or similar so properties are iterated in the order
042 * they were added.
043 *
044 * @author Matthew Pocock
045 * @author Greg Cox
046 *
047 * @since 1.0
048 */
049public abstract class AbstractAnnotation
050
051        implements
052                Annotation,
053                Serializable
054{
055        /**
056         *
057         */
058        private static final long serialVersionUID = 2753449055959952873L;
059
060/**
061         * Implement this to return the Map delegate. Modifying this return value will
062         * modify the properties associated with this annotation.
063         *
064         * From code in the 1.2 version of AbstractAnnotation
065         * This is required for the implementation of an Annotation that
066         *            extends AbstractAnnotation. Where possible implementations
067         *            should be backed with a
068         *            <code>LinkedHashMap</code> or similar so properties are iterated in the order
069         *            they were added.
070         *
071         * @return a Map containing all properties
072         */
073        protected abstract Map getProperties();
074
075        /**
076         * A convenience method to see if we have allocated the properties
077         * Map.
078         * This is required for the implementation of an Annotation that
079         *            extends AbstractAnnotation.
080         * @return true if the properties have been allocated, false otherwise
081         *
082         */
083        protected abstract boolean propertiesAllocated();
084
085
086        @Override
087        public Object getProperty(Object key) throws NoSuchElementException {
088                if(propertiesAllocated()) {
089                        Map prop = getProperties();
090                        if(prop.containsKey(key)) {
091                                return prop.get(key);
092                        }
093                }
094                throw new NoSuchElementException("Property " + key + " unknown");
095        }
096
097        @Override
098        public void setProperty(Object key, Object value)
099         {
100
101                        getProperties().put(key, value);
102
103        }
104
105        @Override
106        public void removeProperty(Object key)
107                throws  NoSuchElementException
108        {
109                if (!getProperties().containsKey(key)) {
110                                throw new NoSuchElementException("Can't remove key " + key.toString());
111                }
112
113
114                        getProperties().remove(key);
115
116        }
117
118        @Override
119        public boolean containsProperty(Object key) {
120                if(propertiesAllocated()) {
121                        return getProperties().containsKey(key);
122                } else {
123                        return false;
124                }
125        }
126
127        @Override
128        public Set keys() {
129                if(propertiesAllocated()) {
130                        return getProperties().keySet();
131                } else {
132                        return Collections.EMPTY_SET;
133                }
134        }
135
136        @Override
137        public String toString() {
138                StringBuffer sb = new StringBuffer("{");
139                Map prop = getProperties();
140                Iterator i = prop.keySet().iterator();
141                if(i.hasNext()) {
142                        Object key = i.next();
143                        sb.append(key).append("=").append(prop.get(key));
144                }
145                while(i.hasNext()) {
146                        Object key = i.next();
147                        sb.append(",").append(key).append("=").append(prop.get(key));
148                }
149                sb.append("}");
150                return sb.substring(0);
151        }
152
153        @Override
154        public Map asMap() {
155                return Collections.unmodifiableMap(getProperties());
156        }
157
158        /**
159         * Protected no-args constructor intended for sub-classes. This class is
160         * abstract and can not be directly instantiated.
161         */
162        protected AbstractAnnotation() {
163        }
164
165        /**
166         * Copy-constructor.
167         *
168         * <p>
169         * This does a shallow copy of the annotation. The result is an annotation
170         * with the same properties and values, but which is independant of the
171         * original annotation.
172         * </p>
173         *
174         * @param ann  the Annotation to copy
175         */
176        protected AbstractAnnotation(Annotation ann) {
177                if(ann == null) {
178                        throw new NullPointerException(
179                                "Null annotation not allowed. Use Annotation.EMPTY_ANNOTATION instead."
180                        );
181                }
182                if(ann == Annotation.EMPTY_ANNOTATION) {
183                        return;
184                }
185                Map properties = getProperties();
186                for(Iterator i = ann.keys().iterator(); i.hasNext(); ) {
187                        Object key = i.next();
188                        try {
189                                properties.put(key, ann.getProperty(key));
190                        } catch (IllegalArgumentException iae) {
191                                throw new RuntimeException(
192                                        "Property was there and then disappeared: " + key, iae
193                                );
194                        }
195                }
196        }
197
198        /**
199         * Create a new Annotation by copying the key-value pairs from a map. The
200         * resulting Annotation is independant of the map.
201         *
202         * @param annMap  the Map to copy from.
203         */
204        public AbstractAnnotation(Map annMap) {
205                if(annMap == null) {
206                        throw new IllegalArgumentException(
207                                "Null annotation Map not allowed. Use an empy map instead."
208                        );
209                }
210                if(annMap.isEmpty()) {
211                        return;
212                }
213
214                Map properties = getProperties();
215                for(Iterator i = annMap.keySet().iterator(); i.hasNext(); ) {
216                        Object key = i.next();
217                        properties.put(key, annMap.get(key));
218                }
219        }
220
221
222        @Override
223        public int hashCode() {
224                return asMap().hashCode();
225        }
226
227        @Override
228        public boolean equals(Object o) {
229                if(o == this){
230                                return true;
231                }
232                if (! (o instanceof Annotation)) {
233                        return false;
234                }
235
236                return ((Annotation) o).asMap().equals(asMap());
237        }
238}