/*
* Copyright to the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.rioproject.opstring;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* ClassBundle provides a mechanism to define the resources needed to load and
* instantiate a class.
*
* @author Dennis Reedy
*/
public class ClassBundle implements Serializable {
@SuppressWarnings("unused")
static final long serialVersionUID = 2L;
/**
* The classname
*/
private String className;
/**
* The URL path used to load the class. The path will be applied to all
* JARs in this ClassBundle
*/
private String codebase;
/**
* Collection of jar names
*/
private final List<String> jarNames = Collections.synchronizedList(new ArrayList<String>());
/**
* An artifact ID.
*/
private String artifact;
/**
* Create a new ClassBundle
*/
public ClassBundle() {
}
/**
* Create a new ClassBundle
*
* @param className The className
*/
public ClassBundle(final String className) {
if(className == null)
throw new IllegalArgumentException("className is null");
this.className = className;
}
/**
* Create a new ClassBundle
*
* @param className The className
* @param jarNames Array of Strings identifying resource names used to load
* the className
* @param codebase The URL path used to load the class. The path will be
* applied to all JARs in this ClassBundle
*/
public ClassBundle(final String className, final String[] jarNames, final String codebase) {
if(className == null)
throw new IllegalArgumentException("className cannot be null");
if(jarNames==null)
throw new IllegalArgumentException("jarNames cannot be null");
this.className = className;
for(String jar : jarNames) {
if(jar!=null) {
this.jarNames.add(jar);
}
}
this.codebase = codebase;
}
/**
* Set the codebase used to load the class. The path will be applied to all
* JARs in this ClassBundle
*
* @param codebase The codebase to set
*/
public void setCodebase(final String codebase) {
this.codebase = codebase;
if(this.codebase!=null) {
if(!this.codebase.endsWith("/"))
this.codebase = this.codebase+"/";
}
}
/**
* Get the codebase used to load the class.
*
* @return The codebase that has been set. If the codebase has properties
* declared (in the form <tt>$[property]</tt>), return a formatted string
* with the properties expanded. If there are no property elements
* declared, return the original string.
*/
public String getCodebase() {
return(translateCodebase());
}
/**
* Get the codebase without any translation
*
* @return The codebase that has been set
*/
public String getRawCodebase() {
return(codebase);
}
/**
* Set the className
*
* @param className The className, suitable for use with Class.forName()
*/
public void setClassName(final String className) {
this.className = className;
}
/**
* Get the className
*
* @return The className, suitable for use with Class.forName()
*/
public String getClassName() {
return (className);
}
/**
* Get the artifact associated with the className
*
* @return The artifact associated with the className
*/
public String getArtifact() {
return artifact;
}
/**
* Set the artifact
*
* @param artifact The artifact associated with the className
*/
public void setArtifact(final String artifact) {
this.artifact = artifact;
}
/**
* Set JARs to the ClassBundle.
*
* @param jars Jar names to set
*/
public void setJARs(final String... jars) {
jarNames.clear();
addJARs(jars);
}
/**
* Add JARs to the ClassBundle.
*
* @param jars Jar names to add.
*/
public void addJARs(final String... jars) {
if(jars == null)
throw new IllegalArgumentException("jars cannot be null");
for(String jar : jars)
addJAR(jar);
}
/**
* Add a JAR to the Collection of JAR resources.
*
* @param jar Name of the JAR to add
*/
public void addJAR(final String jar) {
if(jar == null)
throw new IllegalArgumentException("jar cannot be null");
if(!jarNames.contains(jar))
jarNames.add(jar);
}
/**
* Get the JAR names.
*
* @return A String array of the JAR names. This method will return a new
* array each time. If there are no JAR names, this method will return an
* empty array
*/
public String[] getJARNames() {
return jarNames.toArray(new String[jarNames.size()]);
}
/**
* Get the JAR resources
*
* @return An URL array of the JARs that can be used as a classpath to
* load the class. This method will return a new array each time. If
* there are no JARs configured, this method will return an empty array.
*
* @throws MalformedURLException if the codebase has not been set, or if
* the provided codebase contains an invalid protocol
*/
public URL[] getJARs() throws MalformedURLException {
return (urlsFromJARs(getJARNames()));
}
/**
* Override hashCode
*/
public int hashCode() {
int hc = 17;
hc = 37*hc+(className!=null?className.hashCode():0);
if(!getJARList().isEmpty()) {
hc = 37*hc+getJARList().hashCode();
} else {
hc = 37*hc+(artifact!=null?artifact.hashCode():0);
}
return(hc);
}
/**
* Override equals
*/
public boolean equals(final Object obj) {
if(this == obj)
return(true);
if(!(obj instanceof ClassBundle))
return(false);
ClassBundle that = (ClassBundle)obj;
if(this.className!=null && that.className!=null) {
if(this.className.equals(that.className)) {
if(this.artifact!=null && that.artifact!=null) {
return this.artifact.equals(that.artifact);
} else {
return this.getJARList().equals(that.getJARList());
}
}
}
return(false);
}
/*
* Get URLs for jarNames based on the codebase
*/
private URL[] urlsFromJARs(final String[] jarNames) throws MalformedURLException {
URL[] urls = new URL[jarNames.length];
for(int i = 0; i < urls.length; i++) {
urls[i] = new URL(translateCodebase()+jarNames[i]);
}
return (urls);
}
/*
* Return an ArrayList of JAR names
*/
private ArrayList<String> getJARList() {
ArrayList<String> list = new ArrayList<String>();
String[] jars = getJARNames();
list.addAll(Arrays.asList(jars));
return(list);
}
@Override
public String toString() {
StringBuilder buffer = new StringBuilder();
buffer.append("ClassName=").append(className).append(", ");
buffer.append("Artifact=").append(artifact).append(", ");
buffer.append("Codebase=").append(codebase).append(", ");
String[] jars = getJARNames();
if(jars.length>0) {
buffer.append("Searchpath={");
for(int i=0; i<jars.length; i++) {
if(i>0)
buffer.append(", ");
buffer.append(jars[i]);
}
buffer.append("}");
}
return(buffer.toString());
}
/*
* Expand any properties in the codebase String. Properties are declared
* with the pattern of : <code>$[property]</code>
*
* @return If the codebase has properties declared (in the form
* <code>$[property]</code>), return a formatted string with the
* properties expanded. If there are no property elements declared, return
* the original string.
*
* @throws IllegalArgumentException If a property value cannot be obtained
* an IllegalArgument will be thrown
*/
private String translateCodebase() {
if(codebase==null)
return(codebase);
if(System.getProperty("os.name").startsWith("Win") && codebase.startsWith("file://")) {
codebase = "file:/"+codebase.substring(7);
}
return(codebase);
}
/**
* Merge two ClassBundles
*
* @param bundles ClassBundle instances to merge
*
* @return A merged ClassBundle.
*
* @throws IllegalArgumentException For all ClassBundles that have a
* non-null classname, that classname must be equal. If this is not the
* case then an IllegalArgumentException is thrown.
*/
@SuppressWarnings("unused")
public static ClassBundle merge(final ClassBundle... bundles) {
String className = null;
for(ClassBundle bundle : bundles) {
if(bundle.getClassName()!=null) {
if(className==null)
className = bundle.getClassName();
else if(!className.equals(bundle.getClassName()))
throw new IllegalArgumentException("bundles must have same classname");
}
}
ClassBundle cb = new ClassBundle();
for(ClassBundle bundle : bundles) {
cb.setArtifact(bundle.getArtifact());
cb.addJARs(bundle.getJARNames());
cb.setCodebase(bundle.getCodebase());
cb.setClassName(bundle.getClassName());
}
return cb;
}
}