001package org.biopax.paxtools.controller;
002
003import org.biopax.paxtools.model.BioPAXElement;
004import org.biopax.paxtools.util.AutoComplete;
005import org.biopax.paxtools.util.IllegalBioPAXArgumentException;
006
007import java.lang.reflect.Method;
008import java.util.HashMap;
009import java.util.HashSet;
010import java.util.Map;
011import java.util.Set;
012
013/**
014 * Provides an editor for  all object value types, e.g. everything other than Primitive, ENUM, and String.
015 */
016public class ObjectPropertyEditor<D extends BioPAXElement, R extends BioPAXElement> extends AbstractPropertyEditor<D,
017                R>
018{
019// ------------------------------ FIELDS ------------------------------
020
021        private Map<Class<? extends BioPAXElement>, Set<Class<? extends BioPAXElement>>> restrictedRanges =
022                        new HashMap<Class<? extends BioPAXElement>, Set<Class<? extends BioPAXElement>>>();
023
024        private Method inverseGetMethod;
025
026        private boolean inverseMultipleCardinality;
027
028        private boolean completeForward;
029
030        private boolean completeBackward;
031
032        private PropertyAccessor<R, ? super D> inverseAccessor;
033
034// --------------------------- CONSTRUCTORS ---------------------------
035
036        /**
037         * Full constructor.
038         * @param property Name of the property, e.g. entityReference.
039         * @param getMethod A "Method" class that represents the getter method. e.g. getEntityReference()
040         * @param domain name of the domain of this property. e.g. PhysicalEntity
041         * @param range name of the range of this property. e.g. EntityReference.
042         * @param multipleCardinality false if this property is functional, e.g. many-to-one or one-to-one.
043         */
044        public ObjectPropertyEditor(String property, Method getMethod, final Class<D> domain, final Class<R> range,
045                                    boolean multipleCardinality)
046        {
047                super(property, getMethod, domain, range, multipleCardinality);
048
049                inverseGetMethod = findInverseGetMethod();
050
051                if (inverseGetMethod != null)
052                {
053                        inverseMultipleCardinality = isMultipleCardinality(inverseGetMethod);
054                        this.inverseAccessor = buildInverse(detectRange(inverseGetMethod),range);
055                }
056
057
058                AutoComplete autComp = getGetMethod().getAnnotation(AutoComplete.class);
059
060                if (autComp == null)
061                {
062                        completeForward = true;
063                        completeBackward = false;
064                } else
065                {
066                        completeForward = autComp.forward();
067                        completeBackward = autComp.backward();
068                }
069        }
070
071
072        private <T extends BioPAXElement> SimplePropertyAccessor<R, ? super D> buildInverse(Class<T> inverseRange,
073                                                                                            Class<R> inverseDomain)
074        {
075                return (SimplePropertyAccessor<R, ? super D>) new SimplePropertyAccessor<R, T>(inverseDomain, inverseRange,
076                                                                                               inverseMultipleCardinality,
077                                                                                               inverseGetMethod);
078        }
079
080// --------------------- GETTER / SETTER METHODS ---------------------
081
082        public Map<Class<? extends BioPAXElement>, Set<Class<? extends BioPAXElement>>> getRestrictedRanges()
083        {
084                return restrictedRanges;
085        }
086
087        public boolean isCompleteForward()
088        {
089                return completeForward;
090        }
091
092        public boolean isCompleteBackward()
093        {
094                return completeBackward;
095        }
096
097        public boolean isInverseMultipleCardinality()
098        {
099                return inverseMultipleCardinality;
100        }
101
102        public Method getInverseGetMethod()
103        {
104                return inverseGetMethod;
105        }
106
107        public PropertyAccessor<R, ? super D> getInverseAccessor()
108        {
109                return inverseAccessor;
110        }
111
112        // -------------------------- OTHER METHODS --------------------------
113
114        @Override public String toString()
115        {
116                StringBuilder sb = new StringBuilder(super.toString());
117                for (Class rDomain : restrictedRanges.keySet())
118                {
119                        sb.append(" D:").append(rDomain.getSimpleName()).append("=");
120                        String delim = "";
121                        for (Class<? extends BioPAXElement> range : restrictedRanges.get(rDomain))
122                        {
123                                sb.append(delim).append(range.getSimpleName());
124                                delim = ",";
125                        }
126
127                }
128                return sb.toString();
129        }
130
131        /**
132         * This method adds a range restriction to the property editor. e.g. All entityReferences of Proteins should be
133         * ProteinReferences.
134         *
135         * Note: All restrictions specified in the BioPAX specification is automatically created by the {@link EditorMap}
136         * during initialization. Use this method if you need to add restrictions that are not specified in
137         * the model.
138         * @param domain subdomain of the property to be restricted
139         * @param ranges valid ranges for this subdomain.
140         */
141        public void addRangeRestriction(Class<? extends BioPAXElement> domain, Set<Class<? extends BioPAXElement>> ranges)
142        {
143                this.restrictedRanges.put(domain, ranges);
144        }
145
146        /**
147         * This method sets all range restrictions.
148         *
149         * Note: All restrictions specified in the BioPAX specification is automatically created by the {@link EditorMap}
150         * during initialization. Use this method if you need to add restrictions that are not specified in
151         * the model.
152
153         * @param restrictedRanges a set of range restrictions specified as a map.
154         */
155        public void setRangeRestriction(
156                        Map<Class<? extends BioPAXElement>, Set<Class<? extends BioPAXElement>>> restrictedRanges)
157        {
158                this.restrictedRanges = restrictedRanges;
159        }
160
161        @Override
162        protected void checkRestrictions(R value, D bean)
163        {
164                super.checkRestrictions(value, bean);
165                Set<Class<? extends BioPAXElement>> classes = getRestrictedRangesFor(
166                                (Class<? extends D>) bean.getModelInterface());
167                if (classes != null && !isInstanceOfAtLeastOne(classes, value))
168                {
169                        throw new IllegalBioPAXArgumentException(
170                                "The range restriction is violated; " 
171                                        + "property: " + property
172                                        + ", bean: " + bean + "--> value: " + value);
173                }
174        }
175
176        /**
177         *
178         * @param restrictedDomain a subdomain that is restricted.
179         * @return the range restrictions for the given subdomain for this propertyEditor.
180         */
181        public Set<Class<? extends BioPAXElement>> getRestrictedRangesFor(Class<? extends D> restrictedDomain)
182        {
183                Set<Class<? extends BioPAXElement>> classes = this.restrictedRanges.get(restrictedDomain);
184                if (classes == null)
185                {
186                        classes = new HashSet<Class<? extends BioPAXElement>>();
187                        classes.add(this.getRange());
188                }
189                return classes;
190        }
191
192        /**
193         * @return true iff this property has a defined inverse link in paxtools.
194         */
195        public boolean hasInverseLink()
196        {
197                return getInverseGetMethod() != null;
198        }
199
200        /**
201         * @return the inverse get method for this property. If the property for this editor is entityReference this method
202         * will return a Method instance that represents {@link org.biopax.paxtools.model.level3
203         * .EntityReference#getEntityReferenceOf()}.
204         */
205        protected Method findInverseGetMethod()
206        {
207                String name = getGetMethod().getName() + "Of";
208
209                Method method = null;
210
211                try
212                {
213                        method = getRange().getMethod(name);
214                }
215                catch (NoSuchMethodException e)
216                {
217                        log.debug("Range " + getRange() + " has no inverse method named " + name);
218                }
219
220                return method;
221        }
222
223
224        // --------------------- ACCESORS and MUTATORS---------------------
225
226}