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}