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 */
017package net.dpml.cli.validation;
018
019import java.util.List;
020import java.util.ListIterator;
021
022import net.dpml.cli.resource.ResourceConstants;
023import 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="@PUBLISHER-URL@">@PUBLISHER-NAME@</a>
044 * @version @PROJECT-VERSION@
045 */
046public 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}