001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (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     *
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 org.apache.commons.cli2.validation;
018    
019    import java.util.List;
020    import java.util.ListIterator;
021    
022    import org.apache.commons.cli2.resource.ResourceConstants;
023    import org.apache.commons.cli2.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 John Keyes
044     */
045    public class ClassValidator implements Validator {
046        /** i18n */
047        private static final ResourceHelper resources = ResourceHelper.getResourceHelper();
048    
049        /** whether the class argument is loadable */
050        private boolean loadable;
051    
052        /** whether to create an instance of the class */
053        private boolean instance;
054    
055        /** the classloader to load classes from */
056        private ClassLoader loader;
057    
058        /**
059         * Validate each argument value in the specified List against this instances
060         * permitted attributes.
061         *
062         * If a value is valid then it's <code>String</code> value in the list is
063         * replaced with it's <code>Class</code> value or instance.
064         *
065         * @see org.apache.commons.cli2.validation.Validator#validate(java.util.List)
066         */
067        public void validate(final List values)
068            throws InvalidArgumentException {
069            for (final ListIterator i = values.listIterator(); i.hasNext();) {
070                final String name = (String) i.next();
071    
072                if (!isPotentialClassName(name)) {
073                    throw new InvalidArgumentException(resources.getMessage(ResourceConstants.CLASSVALIDATOR_BAD_CLASSNAME,
074                                                                            name));
075                }
076    
077                if (loadable || instance) {
078                    final ClassLoader theLoader = getClassLoader();
079    
080                    try {
081                        final Class clazz = theLoader.loadClass(name);
082    
083                        if (instance) {
084                            i.set(clazz.newInstance());
085                        } else {
086                            i.set(clazz);
087                        }
088                    } catch (final ClassNotFoundException exp) {
089                        throw new InvalidArgumentException(resources.getMessage(ResourceConstants.CLASSVALIDATOR_CLASS_NOTFOUND,
090                                                                                name));
091                    } catch (final IllegalAccessException exp) {
092                        throw new InvalidArgumentException(resources.getMessage(ResourceConstants.CLASSVALIDATOR_CLASS_ACCESS,
093                                                                                name, exp.getMessage()));
094                    } catch (final InstantiationException exp) {
095                        throw new InvalidArgumentException(resources.getMessage(ResourceConstants.CLASSVALIDATOR_CLASS_CREATE,
096                                                                                name));
097                    }
098                }
099            }
100        }
101    
102        /**
103         * Returns whether the argument value must represent a
104         * class that is loadable.
105         *
106         * @return whether the argument value must represent a
107         * class that is loadable.
108         */
109        public boolean isLoadable() {
110            return loadable;
111        }
112    
113        /**
114         * Specifies whether the argument value must represent a
115         * class that is loadable.
116         *
117         * @param loadable whether the argument value must
118         * represent a class that is loadable.
119         */
120        public void setLoadable(boolean loadable) {
121            this.loadable = loadable;
122        }
123    
124        /**
125         * Returns the {@link ClassLoader} used to resolve and load
126         * the classes specified by the argument values.
127         *
128         * @return the {@link ClassLoader} used to resolve and load
129         * the classes specified by the argument values.
130         */
131        public ClassLoader getClassLoader() {
132            if (loader == null) {
133                loader = getClass().getClassLoader();
134            }
135    
136            return loader;
137        }
138    
139        /**
140         * Specifies the {@link ClassLoader} used to resolve and load
141         * the classes specified by the argument values.
142         *
143         * @param loader the {@link ClassLoader} used to resolve and load
144         * the classes specified by the argument values.
145         */
146        public void setClassLoader(ClassLoader loader) {
147            this.loader = loader;
148        }
149    
150        /**
151         * Returns whether the argument value must represent a
152         * class that can be instantiated.
153         *
154         * @return whether the argument value must represent a
155         * class that can be instantiated.
156         */
157        public boolean isInstance() {
158            return instance;
159        }
160    
161        /**
162         * Specifies whether the argument value must represent a
163         * class that can be instantiated.
164         *
165         * @param instance whether the argument value must
166         * represent a class that can be instantiated.
167         */
168        public void setInstance(boolean instance) {
169            this.instance = instance;
170        }
171    
172        /**
173         * Returns whether the specified name is allowed as
174         * a Java class name.
175         * @param name the name to be checked
176         * @return true if allowed as Java class name
177         */
178        protected boolean isPotentialClassName(final String name) {
179            final char[] chars = name.toCharArray();
180    
181            boolean expectingStart = true;
182    
183            for (int i = 0; i < chars.length; ++i) {
184                final char c = chars[i];
185    
186                if (expectingStart) {
187                    if (!Character.isJavaIdentifierStart(c)) {
188                        return false;
189                    }
190    
191                    expectingStart = false;
192                } else {
193                    if (c == '.') {
194                        expectingStart = true;
195                    } else if (!Character.isJavaIdentifierPart(c)) {
196                        return false;
197                    }
198                }
199            }
200    
201            return !expectingStart;
202        }
203    }