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}