001    /*
002     * Copyright 2003-2005 The Apache Software Foundation
003     * Copyright 2005 Stephen McConnell
004     *
005     * Licensed under the Apache License, Version 2.0 (the "License");
006     * you may not use this file except in compliance with the License.
007     * You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package net.dpml.cli.validation;
018    
019    import java.util.List;
020    import java.util.ListIterator;
021    
022    import net.dpml.cli.resource.ResourceConstants;
023    import net.dpml.cli.resource.ResourceHelper;
024    
025    /**
026     * The <code>ClassValidator</code> validates the string argument
027     * values are class names.
028     *
029     * The following example shows how to validate the 'logger'
030     * argument value is a class name, that can be instantiated.
031     *
032     * <pre>
033     * ...
034     * ClassValidator validator = new ClassValidator();
035     * validator.setInstance(true);
036     *
037     * ArgumentBuilder builder = new ArgumentBuilder();
038     * Argument logger =
039     *     builder.withName("logger");
040     *            .withValidator(validator);
041     * </pre>
042     *
043     * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
044     * @version 1.0.0
045     */
046    public class ClassValidator implements Validator 
047    {
048        /** i18n */
049        private static final ResourceHelper RESOURCES = 
050          ResourceHelper.getResourceHelper();
051    
052        /** whether the class argument is loadable */
053        private boolean m_loadable;
054    
055        /** whether to create an instance of the class */
056        private boolean m_instance;
057    
058        /** the classloader to load classes from */
059        private ClassLoader m_loader;
060    
061       /**
062        * Validate each argument value in the specified List against this instances
063        * permitted attributes.
064        *
065        * If a value is valid then it's <code>String</code> value in the list is
066        * replaced with it's <code>Class</code> value or instance.
067        *
068        * @param values the list of values to validate 
069        * @see net.dpml.cli.validation.Validator#validate(java.util.List)
070        * @exception InvalidArgumentException if a value is invalid
071        */
072        public void validate( final List values ) throws InvalidArgumentException 
073        {
074            for( final ListIterator i = values.listIterator(); i.hasNext();)
075            {
076                final String name = (String) i.next();
077    
078                if( !isPotentialClassName( name ) )
079                {
080                    throw new InvalidArgumentException(
081                      RESOURCES.getMessage(
082                        ResourceConstants.CLASSVALIDATOR_BAD_CLASSNAME,
083                        name ) );
084                }
085    
086                if( m_loadable || m_instance )
087                {
088                    final ClassLoader theLoader = getClassLoader();
089    
090                    try 
091                    {
092                        final Class clazz = theLoader.loadClass( name );
093                        if( m_instance )
094                        {
095                            i.set( clazz.newInstance() );
096                        } 
097                        else 
098                        {
099                            i.set( clazz );
100                        }
101                    } 
102                    catch( final ClassNotFoundException exp )
103                    {
104                        throw new InvalidArgumentException(
105                          RESOURCES.getMessage(
106                            ResourceConstants.CLASSVALIDATOR_CLASS_NOTFOUND,
107                            name ) );
108                    } 
109                    catch( final IllegalAccessException exp )
110                    {
111                        throw new InvalidArgumentException(
112                          RESOURCES.getMessage(
113                            ResourceConstants.CLASSVALIDATOR_CLASS_ACCESS,
114                            name, 
115                            exp.getMessage() ) );
116                    } 
117                    catch( final InstantiationException exp )  
118                    {
119                        throw new InvalidArgumentException(
120                          RESOURCES.getMessage(
121                            ResourceConstants.CLASSVALIDATOR_CLASS_CREATE,
122                            name ) );
123                    }
124                }
125            }
126        }
127    
128        /**
129         * Returns whether the argument value must represent a
130         * class that is loadable.
131         *
132         * @return whether the argument value must represent a
133         * class that is loadable.
134         */
135        public boolean isLoadable() 
136        {
137            return m_loadable;
138        }
139    
140        /**
141         * Specifies whether the argument value must represent a
142         * class that is loadable.
143         *
144         * @param loadable whether the argument value must
145         * represent a class that is loadable.
146         */
147        public void setLoadable( boolean loadable )
148        {
149            m_loadable = loadable;
150        }
151    
152        /**
153         * Returns the {@link ClassLoader} used to resolve and load
154         * the classes specified by the argument values.
155         *
156         * @return the {@link ClassLoader} used to resolve and load
157         * the classes specified by the argument values.
158         */
159        public ClassLoader getClassLoader()
160        {
161            if( m_loader == null )
162            {
163                m_loader = getClass().getClassLoader();
164            }
165            return m_loader;
166        }
167    
168        /**
169         * Specifies the {@link ClassLoader} used to resolve and load
170         * the classes specified by the argument values.
171         *
172         * @param loader the {@link ClassLoader} used to resolve and load
173         * the classes specified by the argument values.
174         */
175        public void setClassLoader( ClassLoader loader ) 
176        {
177            m_loader = loader;
178        }
179    
180        /**
181         * Returns whether the argument value must represent a
182         * class that can be instantiated.
183         *
184         * @return whether the argument value must represent a
185         * class that can be instantiated.
186         */
187        public boolean isInstance()
188        {
189            return m_instance;
190        }
191    
192        /**
193         * Specifies whether the argument value must represent a
194         * class that can be instantiated.
195         *
196         * @param instance whether the argument value must
197         * represent a class that can be instantiated.
198         */
199        public void setInstance( boolean instance )
200        {
201            m_instance = instance;
202        }
203    
204        /**
205         * Returns whether the specified name is allowed as
206         * a Java class name.
207         * @param name the potential classname
208         * @return true if the name is a potential classname
209         */
210        protected boolean isPotentialClassName( final String name ) 
211        {
212            final char[] chars = name.toCharArray();
213    
214            boolean expectingStart = true;
215    
216            for( int i = 0; i < chars.length; ++i ) 
217            {
218                final char c = chars[i];
219    
220                if( expectingStart ) 
221                {
222                    if( !Character.isJavaIdentifierStart( c ) )
223                    {
224                        return false;
225                    }
226                    expectingStart = false;
227                } 
228                else 
229                {
230                    if( c == '.' ) 
231                    {
232                        expectingStart = true;
233                    } 
234                    else if( !Character.isJavaIdentifierPart( c ) ) 
235                    {
236                        return false;
237                    }
238                }
239            }
240    
241            return !expectingStart;
242        }
243    }