001    /*
002     * JBoss DNA (http://www.jboss.org/dna)
003     * See the COPYRIGHT.txt file distributed with this work for information
004     * regarding copyright ownership.  Some portions may be licensed
005     * to Red Hat, Inc. under one or more contributor license agreements.
006     * See the AUTHORS.txt file in the distribution for a full listing of 
007     * individual contributors. 
008     *
009     * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
010     * is licensed to you under the terms of the GNU Lesser General Public License as
011     * published by the Free Software Foundation; either version 2.1 of
012     * the License, or (at your option) any later version.
013     *
014     * JBoss DNA is distributed in the hope that it will be useful,
015     * but WITHOUT ANY WARRANTY; without even the implied warranty of
016     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017     * Lesser General Public License for more details.
018     *
019     * You should have received a copy of the GNU Lesser General Public
020     * License along with this software; if not, write to the Free
021     * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
022     * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
023     */
024    package org.jboss.dna.common.component;
025    
026    import java.util.ArrayList;
027    import java.util.Collections;
028    import java.util.HashMap;
029    import java.util.List;
030    import java.util.Map;
031    import net.jcip.annotations.Immutable;
032    import org.jboss.dna.common.CommonI18n;
033    import org.jboss.dna.common.util.CheckArg;
034    import org.jboss.dna.common.util.ClassUtil;
035    
036    /**
037     * @author Randall Hauch
038     */
039    @Immutable
040    public class ComponentConfig implements Comparable<ComponentConfig> {
041    
042        private final String name;
043        private final String description;
044        private final String componentClassname;
045        private final List<String> classpath;
046        private final Map<String, Object> properties;
047        private final long timestamp;
048    
049        /**
050         * Create a component configuration.
051         * 
052         * @param name the name of the configuration, which is considered to be a unique identifier
053         * @param description the description
054         * @param classname the name of the Java class used for the component
055         * @param classpath the optional classpath (defined in a way compatible with a {@link ClassLoaderFactory}
056         * @throws IllegalArgumentException if the name is null, empty or blank, or if the classname is null, empty or not a valid
057         *         Java classname
058         */
059        public ComponentConfig( String name,
060                                String description,
061                                String classname,
062                                String... classpath ) {
063            this(name, description, System.currentTimeMillis(), Collections.<String, Object>emptyMap(), classname, classpath);
064        }
065    
066        /**
067         * Create a component configuration.
068         * 
069         * @param name the name of the configuration, which is considered to be a unique identifier
070         * @param description the description
071         * @param properties the mapping of properties to values that should be set through reflection after a component is
072         *        instantiated with this configuration information
073         * @param classname the name of the Java class used for the component
074         * @param classpath the optional classpath (defined in a way compatible with a {@link ClassLoaderFactory}
075         * @throws IllegalArgumentException if the name is null, empty or blank, or if the class name is null, empty or not a valid
076         *         Java class name
077         */
078        public ComponentConfig( String name,
079                                String description,
080                                Map<String, Object> properties,
081                                String classname,
082                                String... classpath ) {
083            this(name, description, System.currentTimeMillis(), properties, classname, classpath);
084        }
085    
086        /**
087         * Create a component configuration.
088         * 
089         * @param name the name of the configuration, which is considered to be a unique identifier
090         * @param description the description
091         * @param timestamp the timestamp that this component was last changed
092         * @param properties the mapping of properties to values that should be set through reflection after a component is
093         *        instantiated with this configuration information
094         * @param classname the name of the Java class used for the component
095         * @param classpath the optional classpath (defined in a way compatible with a {@link ClassLoaderFactory}
096         * @throws IllegalArgumentException if the name is null, empty or blank, or if the classname is null, empty or not a valid
097         *         Java classname
098         */
099        public ComponentConfig( String name,
100                                String description,
101                                long timestamp,
102                                Map<String, Object> properties,
103                                String classname,
104                                String... classpath ) {
105            CheckArg.isNotEmpty(name, "name");
106            this.name = name.trim();
107            this.description = description != null ? description.trim() : "";
108            this.componentClassname = classname;
109            this.classpath = buildList(classpath);
110            this.timestamp = timestamp;
111            this.properties = properties != null ? Collections.unmodifiableMap(new HashMap<String, Object>(properties)) : Collections.<String, Object>emptyMap();
112    
113            // Check the classname is a valid classname ...
114            if (!ClassUtil.isFullyQualifiedClassname(classname)) {
115                throw new IllegalArgumentException(CommonI18n.componentClassnameNotValid.text(classname, name));
116            }
117        }
118    
119        /* package */static List<String> buildList( String... classpathElements ) {
120            List<String> classpath = null;
121            if (classpathElements != null) {
122                classpath = new ArrayList<String>();
123                for (String classpathElement : classpathElements) {
124                    if (!classpath.contains(classpathElement)) classpath.add(classpathElement);
125                }
126                classpath = Collections.unmodifiableList(classpath);
127            } else {
128                classpath = Collections.emptyList(); // already immutable
129            }
130            return classpath;
131        }
132    
133        /**
134         * Get the name of this component.
135         * 
136         * @return the component name; never null, empty or blank
137         */
138        public String getName() {
139            return this.name;
140        }
141    
142        /**
143         * Get the description for this component
144         * 
145         * @return the description
146         */
147        public String getDescription() {
148            return this.description;
149        }
150    
151        /**
152         * Get the fully-qualified name of the Java class used for instances of this component
153         * 
154         * @return the Java class name of this component; never null or empty and always a valid Java class name
155         */
156        public String getComponentClassname() {
157            return this.componentClassname;
158        }
159    
160        /**
161         * Get the classpath defined in terms of strings compatible with a {@link ClassLoaderFactory}.
162         * 
163         * @return the classpath; never null but possibly empty
164         */
165        public List<String> getComponentClasspath() {
166            return this.classpath;
167        }
168    
169        /**
170         * Get the classpath defined as an array of strings compatible with a {@link ClassLoaderFactory}.
171         * 
172         * @return the classpath as an array; never null but possibly empty
173         */
174        public String[] getComponentClasspathArray() {
175            return this.classpath.toArray(new String[this.classpath.size()]);
176        }
177    
178        /**
179         * Get the system timestamp when this configuration object was created.
180         * 
181         * @return the timestamp
182         */
183        public long getTimestamp() {
184            return this.timestamp;
185        }
186    
187        /**
188         * Get the (unmodifiable) properties to be set through reflection on components of this type after instantiation
189         * 
190         * @return the properties to be set through reflection on components of this type after instantiation; never null
191         */
192        public Map<String, Object> getProperties() {
193            return this.properties;
194        }
195    
196        /**
197         * {@inheritDoc}
198         */
199        public int compareTo( ComponentConfig that ) {
200            if (that == this) return 0;
201            int diff = this.getName().compareToIgnoreCase(that.getName());
202            if (diff != 0) return diff;
203            diff = (int)(this.getTimestamp() - that.getTimestamp());
204            return diff;
205        }
206    
207        /**
208         * {@inheritDoc}
209         */
210        @Override
211        public int hashCode() {
212            return this.getName().hashCode();
213        }
214    
215        /**
216         * {@inheritDoc}
217         */
218        @Override
219        public boolean equals( Object obj ) {
220            if (obj == this) return true;
221            if (obj instanceof ComponentConfig) {
222                ComponentConfig that = (ComponentConfig)obj;
223                if (!this.getClass().equals(that.getClass())) return false;
224                return this.getName().equalsIgnoreCase(that.getName());
225            }
226            return false;
227        }
228    
229        /**
230         * Determine whether this component has changed with respect to the supplied component. This method basically checks all
231         * attributes, whereas {@link #equals(Object) equals} only checks the {@link #getClass() type} and {@link #getName()}.
232         * 
233         * @param component the component to be compared with this one
234         * @return true if this componet and the supplied component have some changes, or false if they are exactly equivalent
235         * @throws IllegalArgumentException if the supplied component reference is null or is not the same {@link #getClass() type} as
236         *         this object
237         */
238        public boolean hasChanged( ComponentConfig component ) {
239            CheckArg.isNotNull(component, "component");
240            CheckArg.isInstanceOf(component, this.getClass(), "component");
241            if (!this.getName().equalsIgnoreCase(component.getName())) return true;
242            if (!this.getDescription().equals(component.getDescription())) return true;
243            if (!this.getComponentClassname().equals(component.getComponentClassname())) return true;
244            if (!this.getComponentClasspath().equals(component.getComponentClasspath())) return true;
245            return false;
246        }
247    
248    }