001    /*
002     * Copyright (c) 1998-2014 ChemAxon Ltd. All Rights Reserved.
003     *
004     * This software is the confidential and proprietary information of
005     * ChemAxon. You shall not disclose such Confidential Information
006     * and shall use it only in accordance with the terms of the agreements
007     * you entered into with ChemAxon.
008     *
009     */
010    
011    package com.chemaxon.apidiscovery;
012    
013    import com.chemaxon.apidiscovery.annotations.Creator;
014    import com.chemaxon.apidiscovery.annotations.Description;
015    import com.chemaxon.apidiscovery.annotations.Parametrized;
016    import com.chemaxon.apidiscovery.interfaces.ParameterBuilder;
017    import com.chemaxon.common.annotations.PublicAPI;
018    import com.google.common.annotations.Beta;
019    import com.google.common.base.Optional;
020    import java.lang.reflect.InvocationTargetException;
021    import java.lang.reflect.Method;
022    import org.apache.commons.logging.Log;
023    import org.apache.commons.logging.LogFactory;
024    
025    /**
026     * CreatorWrapper acts as a type- and instance -dependent service loader.
027     *
028     * <p>A creator method simply creates an instance of a type from an instance of a parameter type. (Consider here
029     * the Descriptors API createXXXComparator methods.) When the parameter type has free parameters it should be
030     * implemented as an immutable parameter object - builder class pair. This wrapper usually wraps a default instance
031     * of such immutable parameter objects; however it returns an associated builder to change.</p>
032     *
033     * <p>Please note that this class is marked with {@link Beta} annotation, so it can be subject of incompatible changes
034     * or removal in later releases.</p>
035     *
036     * @param <T> Type of the created instance
037     *
038     * @author Gabor Imre
039     */
040    
041    @Beta
042    @PublicAPI
043    public class CreatorWrapper<T> {
044    
045        /**
046         * Logger used.
047         */
048        private final Log log = LogFactory.getLog(CreatorWrapper.class);
049    
050        /**
051         * Instance on which the creator method should be called.
052         */
053        private final Object creatorHostObject;
054    
055        /**
056         * Represented creator method.
057         */
058        private final Method creatorMethod;
059    
060        /**
061         * Creator annotation of the represented creator method.
062         */
063        private final Creator creatorAnnotation;
064    
065        /**
066         * Argument object to be passed upon creation.
067         */
068        private final Object initialParameterObject;
069    
070        /**
071         * Builder for the parameter object if exists.
072         */
073        private final Optional<ParameterBuilder<Object>> builder;
074    
075        /**
076         * Encapsulated description of the represented parameter object.
077         */
078        private final Description desc;
079    
080        /**
081         * Construct a wrapper.
082         *
083         * @param creatorHost Object on which a represented creator method will be called
084         * @param creatorMethod The creator method which should be called
085         * @param initialParameter The parameter reference which can be passed to the creator method
086         * @throws IllegalArgumentException when a builder or parametrized object is passed as initialParameter
087         */
088        CreatorWrapper(Object creatorHost, Method creatorMethod, Object initialParameter) {
089            if (this.log.isDebugEnabled()) {
090                this.log.debug("Wrap " + creatorMethod.toString() + " on object " + creatorHost.toString()
091                        + " for parameter " + initialParameter.toString());
092            }
093    
094            // check if creator annotation is present on the represented creator method
095            if (!creatorMethod.isAnnotationPresent(Creator.class)) {
096                throw new IllegalArgumentException("Creator annotation is not present on creator method "
097                        + creatorMethod.toString());
098            }
099            this.creatorAnnotation = creatorMethod.getAnnotation(Creator.class);
100    
101            // check if we got a mutable parameter class
102            if (initialParameter instanceof ParameterBuilder) {
103                throw new IllegalArgumentException("Initial parameter " + initialParameter + " ("
104                        + initialParameter.getClass().getCanonicalName() + ") can not be a ParameterBuilder");
105            }
106    
107            if (initialParameter.getClass().isAnnotationPresent(Parametrized.class)) {
108                throw new IllegalArgumentException("Initial parameter " + initialParameter + " ("
109                        + initialParameter.getClass().getCanonicalName() + ") can not be a mutable parametrized class");
110            }
111    
112            // retrieve builder if exists
113            this.builder = Discovery.initializeBuilderIfExists(initialParameter);
114    
115    
116            this.creatorHostObject = creatorHost;
117            this.creatorMethod = creatorMethod;
118            this.initialParameterObject = initialParameter;
119            this.desc = Discovery.getDescriptionAnnotation(initialParameter);
120        }
121    
122        /**
123         * Order stored in creator annotation.
124         *
125         * @return   order from creator annotation
126         */
127        int getOrder() {
128            return this.creatorAnnotation.order();
129        }
130    
131        /**
132         * Get the parameter object or its mutable builder if exists.
133         *
134         * <p>If the returned object is mutable then its state will be considered upon creation</p>
135         *
136         * @return  The wrapped parameter object or its builder when exists.
137         */
138        public Object getBuilderOrParameterObject() {
139            if (this.builder.isPresent()) {
140                return this.builder.get();
141            } else {
142                return this.initialParameterObject;
143            }
144        }
145    
146        public String getShortName() {
147            return this.desc.shortName();
148        }
149    
150        public String getName() {
151            return this.desc.name();
152        }
153    
154        public String getDescription() {
155            return this.desc.description();
156        }
157    
158        /**
159         * Invoke creator method.
160         *
161         * <p>Use builder if exists, otherwise initial</p>
162         *
163         * @return The created instance
164         */
165        public T create() {
166            // Parameter object to call on
167            final Object parameterToUse;
168    
169            // decide if we should use the builder or the initial
170            if (this.builder.isPresent()) {
171                if (this.log.isDebugEnabled()) {
172                    this.log.debug("Builder present (" + this.builder.get()
173                            + "); invoke its build() method to get parameter object");
174                }
175                parameterToUse = this.builder.get().build();
176            } else {
177                if (this.log.isDebugEnabled()) {
178                    this.log.debug("No builder present, use initial parameter object");
179                }
180                parameterToUse = this.initialParameterObject;
181            }
182    
183            if (this.log.isDebugEnabled()) {
184                this.log.debug("Invoke " + this.creatorMethod.toString() + " on object " + this.creatorHostObject.toString()
185                        + " for parameter " + parameterToUse.toString());
186            }
187    
188            try {
189                return (T) this.creatorMethod.invoke(this.creatorHostObject, parameterToUse);
190            } catch (IllegalAccessException e) {
191                throw new IllegalArgumentException("Internal error", e);
192            } catch (InvocationTargetException e) {
193                throw new IllegalArgumentException("Internal error", e);
194            }
195        }
196    
197    }