001/* 002 * **** BEGIN LICENSE BLOCK ***** 003 * Version: MPL 1.1/GPL 2.0/LGPL 2.1 004 * 005 * The contents of this file are subject to the Mozilla Public License Version 006 * 1.1 (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * http://www.mozilla.org/MPL/ 009 * 010 * Software distributed under the License is distributed on an "AS IS" basis, 011 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 012 * for the specific language governing rights and limitations under the 013 * License. 014 * 015 * The Original Code is part of dcm4che, an implementation of DICOM(TM) in 016 * Java(TM), hosted at https://github.com/gunterze/dcm4che. 017 * 018 * The Initial Developer of the Original Code is 019 * Agfa Healthcare. 020 * Portions created by the Initial Developer are Copyright (C) 2014 021 * the Initial Developer. All Rights Reserved. 022 * 023 * Contributor(s): 024 * See @authors listed below 025 * 026 * Alternatively, the contents of this file may be used under the terms of 027 * either the GNU General Public License Version 2 or later (the "GPL"), or 028 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 029 * in which case the provisions of the GPL or the LGPL are applicable instead 030 * of those above. If you wish to allow use of your version of this file only 031 * under the terms of either the GPL or the LGPL, and not to allow others to 032 * use your version of this file under the terms of the MPL, indicate your 033 * decision by deleting the provisions above and replace them with the notice 034 * and other provisions required by the GPL or the LGPL. If you do not delete 035 * the provisions above, a recipient may use your version of this file under 036 * the terms of any one of the MPL, the GPL or the LGPL. 037 * 038 * ***** END LICENSE BLOCK ***** 039 */ 040package org.dcm4che3.conf.core.api.internal; 041 042import org.dcm4che3.conf.core.api.ConfigurableProperty; 043import org.dcm4che3.conf.core.api.ConfigurableProperty.ConfigurablePropertyType; 044import org.dcm4che3.conf.core.api.Configuration; 045import org.dcm4che3.conf.core.api.ConfigurationException; 046import org.dcm4che3.conf.core.api.LDAP; 047 048import java.lang.annotation.Annotation; 049import java.lang.reflect.InvocationTargetException; 050import java.lang.reflect.Method; 051import java.lang.reflect.ParameterizedType; 052import java.lang.reflect.Type; 053import java.util.ArrayList; 054import java.util.Arrays; 055import java.util.Collection; 056import java.util.HashMap; 057import java.util.List; 058import java.util.Map; 059 060/** 061 * This class shall NOT be referenced externally, it will be removed/renamed/refactored without notice. 062 * 063 * @author Roman K 064 */ 065 066public class ConfigProperty 067{ 068 069 private static final LDAP dummyLdapAnno = DummyConfigurableClass.class.getAnnotation( LDAP.class ); 070 private static final Map<Type, Annotation> dummyAnnotations = new HashMap<Type, Annotation>(); 071 072 static 073 { 074 try 075 { 076 ConfigurableProperty configurableProperty = DummyConfigurableClass.class.getField( "dummy" ).getAnnotation( ConfigurableProperty.class ); 077 dummyAnnotations.put( ConfigurableProperty.class, configurableProperty ); 078 } 079 catch ( NoSuchFieldException e ) 080 { 081 throw new RuntimeException( "That was unexpected" ); 082 } 083 } 084 085 private final Map<Type, Annotation> annotations; 086 087 private final ConfigurableProperty configurablePropertyAnnotation; 088 private final String description; 089 private LDAP ldapAnnotation; 090 091 private final Type type; 092 private final Class rawType; 093 094 private final String name; 095 private final String annotatedName; 096 097 private final boolean isReference; 098 private final boolean isUuid; 099 private final boolean isCollectionOfConfObjects; 100 private final boolean isMapOfConfObjects; 101 private final boolean isOlockHash; 102 private final boolean isExtensionsProperty; 103 private final boolean isConfObject; 104 private final boolean isCollection; 105 private final boolean isCollectionOfReferences; 106 private final boolean isArrayOfConfObjects; 107 private final boolean isArray; 108 private final boolean isMap; 109 private final boolean isWeakReference; 110 111 private final ConfigProperty pseudoPropertyForCollectionElement; 112 private final ConfigProperty pseudoPropertyForConfigClassCollectionElement; 113 114 private final Method valueOfMethod; 115 private final ConfigurableProperty.EnumRepresentation enumRepresentation; 116 private final Enum[] enumValues; 117 118 private final Type[] genericsTypes; 119 private final String defaultValue; 120 121 private final String label; 122 private int order; 123 private Object group; 124 125 public ConfigProperty( Map<Type, Annotation> annotations, String name, Type type ) 126 { 127 this.annotations = annotations; 128 this.name = name; 129 this.type = type; 130 131 // Annotations 132 configurablePropertyAnnotation = (ConfigurableProperty)this.annotations.get( ConfigurableProperty.class ); 133 ldapAnnotation = (LDAP)this.annotations.get( LDAP.class ); 134 135 if ( ldapAnnotation == null ) 136 ldapAnnotation = dummyLdapAnno; 137 138 // Raw type 139 if ( this.type instanceof ParameterizedType ) 140 this.rawType = (Class)((ParameterizedType)this.type).getRawType(); 141 else 142 { 143 this.rawType = (Class)this.type; 144 } 145 146 isOlockHash = configurablePropertyAnnotation.type().equals( ConfigurablePropertyType.OptimisticLockingHash ); 147 isUuid = configurablePropertyAnnotation.type().equals( ConfigurablePropertyType.UUID ); 148 149 //// annotated name //// 150 if ( isUuid ) 151 { 152 annotatedName = Configuration.UUID_KEY; 153 } 154 else if ( isOlockHash ) 155 { 156 annotatedName = Configuration.OLOCK_HASH_KEY; 157 } 158 else if ( configurablePropertyAnnotation.name().isEmpty() ) 159 { 160 161 annotatedName = this.name; 162 163 if ( annotatedName == null ) 164 throw new ConfigurationException( "Property name not specified" ); 165 } 166 else 167 { 168 annotatedName = configurablePropertyAnnotation.name(); 169 } 170 171 defaultValue = configurablePropertyAnnotation.defaultValue(); 172 173 if ( this.type instanceof ParameterizedType ) 174 { 175 genericsTypes = ((ParameterizedType)this.type).getActualTypeArguments(); 176 } 177 else 178 genericsTypes = null; 179 180 isReference = configurablePropertyAnnotation.isReference() || 181 configurablePropertyAnnotation.type().equals( ConfigurablePropertyType.Reference ); 182 183 isWeakReference = configurablePropertyAnnotation.weakReference(); 184 185 isExtensionsProperty = configurablePropertyAnnotation.isExtensionsProperty() || 186 configurablePropertyAnnotation.type().equals( ConfigurablePropertyType.ExtensionsProperty ); 187 188 isConfObject = ConfigReflection.isConfigurableClass( getRawClass() ); 189 190 isCollection = Collection.class.isAssignableFrom( getRawClass() ); 191 192 isCollectionOfReferences = configurablePropertyAnnotation.collectionOfReferences() 193 || configurablePropertyAnnotation.type().equals( ConfigurablePropertyType.CollectionOfReferences ); 194 195 isMapOfConfObjects = Map.class.isAssignableFrom( getRawClass() ) 196 && getPseudoPropertyForGenericsParamater( 1 ).isConfObject() 197 && !isCollectionOfReferences() 198 && !isExtensionsProperty(); 199 200 isCollectionOfConfObjects = Collection.class.isAssignableFrom( getRawClass() ) 201 && ConfigReflection.isConfigurableClass( getPseudoPropertyForGenericsParamater( 0 ).getRawClass() ) 202 && !isCollectionOfReferences(); 203 204 isArrayOfConfObjects = getRawClass().isArray() 205 && ConfigReflection.isConfigurableClass( getRawClass().getComponentType() ) 206 && !isCollectionOfReferences(); 207 208 isArray = rawType.isArray(); 209 210 isMap = Map.class.isAssignableFrom( getRawClass() ); 211 212 pseudoPropertyForCollectionElement = calcPseudoPropertyForCollectionElement(); 213 pseudoPropertyForConfigClassCollectionElement = calcPseudoPropertyForConfigClassCollectionElement(); 214 215 if ( rawType.isEnum() ) 216 { 217 try 218 { 219 valueOfMethod = rawType.getMethod( "valueOf", String.class ); 220 221 enumRepresentation = configurablePropertyAnnotation.enumRepresentation(); 222 223 Method valuesMethod = ((Class)getType()).getMethod( "values" ); 224 enumValues = (Enum[])valuesMethod.invoke( null ); 225 226 } 227 catch ( Exception e ) 228 { 229 throw new RuntimeException( "Unexpected error while working with enum's methods", e ); 230 } 231 232 } 233 else 234 { 235 valueOfMethod = null; 236 enumRepresentation = null; 237 enumValues = null; 238 } 239 240 label = !configurablePropertyAnnotation.label().isEmpty() || isOlockHash || isUuid ? 241 configurablePropertyAnnotation.label() : 242 camelCaseToReadableLabel( annotatedName ); 243 244 description = configurablePropertyAnnotation.description(); 245 246 order = configurablePropertyAnnotation.order(); 247 248 group = configurablePropertyAnnotation.group(); 249 250 } 251 252 public String getLabel() 253 { 254 return label; 255 } 256 257 public String camelCaseToReadableLabel( String annotatedName ) 258 { 259 return annotatedName.replaceAll( "(?<=[A-Z])(?=[A-Z][a-z])|(?<=[^A-Z])(?=[A-Z])|(?<=[A-Za-z])(?=[^A-Za-z])", " " ); 260 } 261 262 public ConfigProperty( Map<Type, Annotation> annotations, Type type ) 263 { 264 this( annotations, "dummy", type ); 265 } 266 267 public ConfigProperty( Type type ) 268 { 269 this( dummyAnnotations, type ); 270 } 271 272 public ConfigProperty clone() 273 { 274 return new ConfigProperty( 275 annotations, 276 getName(), 277 getType() 278 ); 279 } 280 281 public Enum<?> getEnumValueFor( String enumString ) 282 { 283 try 284 { 285 return (Enum<?>)this.valueOfMethod.invoke( null, enumString ); 286 } 287 catch ( IllegalAccessException e ) 288 { 289 throw new RuntimeException( "Unexpected error while working with enum's methods", e ); 290 } 291 catch ( InvocationTargetException e ) 292 { 293 throw new ConfigurationException( "Error while trying to convert '" + enumString + "' to enum [" + getRawClass() + "]", e.getCause() ); 294 } 295 } 296 297 public List<ConfigurableProperty.Tag> getTags() 298 { 299 if ( configurablePropertyAnnotation == null ) 300 return new ArrayList<ConfigurableProperty.Tag>(); 301 302 return new ArrayList<ConfigurableProperty.Tag>( Arrays.asList( configurablePropertyAnnotation.tags() ) ); 303 } 304 305 //TODO: cache 306 public ConfigProperty getPseudoPropertyForGenericsParamater( int genericParameterIndex ) 307 { 308 309 Type typeForGenericsParameter = getTypeForGenericsParameter( genericParameterIndex ); 310 311 return new ConfigProperty( 312 annotations, 313 typeForGenericsParameter 314 ); 315 } 316 317 @Override 318 public String toString() 319 { 320 return "ConfigProperty[name='" + name + "', annotatedName='" + annotatedName + "', rawType='" + rawType + "']"; 321 } 322 323 public boolean isExtensionsProperty() 324 { 325 return isExtensionsProperty; 326 } 327 328 public String getDefaultValue() 329 { 330 return defaultValue; 331 } 332 333 public boolean isOlockHash() 334 { 335 return isOlockHash; 336 } 337 338 public boolean isCollectionOfReferences() 339 { 340 return isCollectionOfReferences; 341 } 342 343 public boolean isReference() 344 { 345 return isReference; 346 } 347 348 public String getAnnotatedName() 349 { 350 return annotatedName; 351 } 352 353 public boolean isUuid() 354 { 355 return isUuid; 356 } 357 358 public boolean isWeakReference() 359 { 360 return isWeakReference; 361 } 362 363 public Type getTypeForGenericsParameter( int genericParameterIndex ) 364 { 365 return genericsTypes[ genericParameterIndex ]; 366 } 367 368 /** 369 * get type of generic/component for collection/Array, Value type for map 370 * 371 * @return 372 */ 373 public ConfigProperty getPseudoPropertyForConfigClassCollectionElement() 374 { 375 return pseudoPropertyForConfigClassCollectionElement; 376 } 377 378 public ConfigProperty calcPseudoPropertyForConfigClassCollectionElement() 379 { 380 381 Type type; 382 if ( isMapOfConfObjects() ) 383 type = getTypeForGenericsParameter( 1 ); 384 else if ( isCollectionOfConfObjects() ) 385 type = getTypeForGenericsParameter( 0 ); 386 else if ( isArrayOfConfObjects() ) 387 type = getRawClass().getComponentType(); 388 else 389 return null; 390 //throw new IllegalArgumentException("This property is not a collection/array/map - "+getType()); 391 392 return new ConfigProperty( annotations, type ); 393 } 394 395 /** 396 * get type of generic/component for collection/Array, Value type for map 397 * Just copies other annotation parameters. 398 * 399 * @return null if not a collection/map 400 */ 401 402 public ConfigProperty getPseudoPropertyForCollectionElement() 403 { 404 return pseudoPropertyForCollectionElement; 405 } 406 407 private ConfigProperty calcPseudoPropertyForCollectionElement() 408 { 409 410 Type type; 411 if ( Map.class.isAssignableFrom( getRawClass() ) ) 412 type = getTypeForGenericsParameter( 1 ); 413 else if ( Collection.class.isAssignableFrom( getRawClass() ) ) 414 type = getTypeForGenericsParameter( 0 ); 415 else if ( getRawClass().isArray() ) 416 type = getRawClass().getComponentType(); 417 else 418 return null; 419 //throw new IllegalArgumentException("This property is not a collection/array/map - "+getType()); 420 421 // TODO: only specific params shold be cloned... 422 // TODO: to get rid of getReferenceAdapter, we need to create child pseudoProp with reference=true in case of collectionOfReferences 423 424 return new ConfigProperty( 425 annotations, 426 type 427 ); 428 } 429 430 @SuppressWarnings( "unchecked" ) 431 public <T> T getAnnotation( Class<T> annotationType ) 432 { 433 if ( annotationType.equals( ConfigurableProperty.class ) ) 434 return (T)configurablePropertyAnnotation; 435 436 if ( annotationType.equals( LDAP.class ) ) 437 return (T)ldapAnnotation; 438 439 return (T)annotations.get( annotationType ); 440 } 441 442 public Class getRawClass() 443 { 444 return rawType; 445 } 446 447 public boolean isMap() 448 { 449 return isMap; 450 } 451 452 public boolean isCollection() 453 { 454 return isCollection; 455 } 456 457 public boolean isArray() 458 { 459 return isArray; 460 } 461 462 public Type getType() 463 { 464 return type; 465 } 466 467 public String getName() 468 { 469 return name; 470 } 471 472 public boolean isConfObject() 473 { 474 return isConfObject; 475 } 476 477 public boolean isMapOfConfObjects() 478 { 479 return isMapOfConfObjects; 480 } 481 482 public boolean isCollectionOfConfObjects() 483 { 484 return isCollectionOfConfObjects; 485 } 486 487 public boolean isArrayOfConfObjects() 488 { 489 return isArrayOfConfObjects; 490 } 491 492 public Enum[] getEnumValues() 493 { 494 return enumValues; 495 } 496 497 public ConfigurableProperty.EnumRepresentation getEnumRepresentation() 498 { 499 return enumRepresentation; 500 } 501 502 public String getDescription() 503 { 504 return description; 505 } 506 507 public Object getOrder() 508 { 509 return order; 510 } 511 512 public Object getGroup() 513 { 514 return group; 515 } 516}