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 */
017package org.apache.commons.cli2.validation;
018
019import java.util.List;
020import java.util.ListIterator;
021
022import org.apache.commons.cli2.resource.ResourceConstants;
023import 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 */
045public 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}