/*******************************************************************************
* Copyright (c) 1998, 2015 Oracle and/or its affiliates. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
* which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Oracle - initial API and implementation from Oracle TopLink
******************************************************************************/
package org.eclipse.persistence.tools.workbench.mappingsmodel.spi.meta.classloader;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.eclipse.persistence.tools.workbench.mappingsmodel.spi.meta.ExternalClassDescription;
import org.eclipse.persistence.tools.workbench.mappingsmodel.spi.meta.ExternalClassNotFoundException;
import org.eclipse.persistence.tools.workbench.mappingsmodel.spi.meta.ExternalClassRepository;
import org.eclipse.persistence.tools.workbench.utility.Classpath;
import org.eclipse.persistence.tools.workbench.utility.string.StringTools;
/**
* Consolidate the state and behavior common to the URL and
* system external class repositories. They both maintain a collection of
* class descriptions, keyed by name, and a class loader that can load the Java
* classes needed by the external classes. Both of these are
* "lazy-initialized".
*/
abstract class AbstractCLExternalClassRepository
implements ExternalClassRepository
{
/**
* The external class descriptions for this repository - keyed by name, since, with a single
* classloader, we can only support one class per class name.
*/
private Map classDescriptions; // pseudo-final
/**
* The classloader that should be able to load the classes
* corresponding to the class descriptions contained by this repository.
*/
private ClassLoader classLoader; // pseudo-final
/**
* The external array types for this repository - keyed by Java Class.
* This is built as the types are requested.
*/
private final Map arrayClassDescriptions;
// ********** constructors **********
/**
* Default constructor.
*/
AbstractCLExternalClassRepository() { // private-protected
super();
this.arrayClassDescriptions = new HashMap();
}
// ********** ExternalClassRepository implementation **********
/**
* @see org.eclipse.persistence.tools.workbench.mappingsmodel.spi.meta.ExternalClassRepository#getExternalClassDescription(String)
*/
public ExternalClassDescription getClassDescription(String className) {
// lazy initialize to postpone the suffering until required
synchronized (this) {
if (this.classDescriptions == null) {
this.classDescriptions = this.buildClassDescriptions();
}
}
return (ExternalClassDescription) this.classDescriptions.get(className);
}
/**
* @see org.eclipse.persistence.tools.workbench.mappingsmodel.spi.meta.ExternalClassRepository#getExternalClassDescriptions()
*/
public ExternalClassDescription[] getClassDescriptions() {
// lazy initialize to postpone the suffering until required
synchronized (this) {
if (this.classDescriptions == null) {
this.classDescriptions = this.buildClassDescriptions();
}
}
return (ExternalClassDescription[]) this.classDescriptions.values().toArray(new ExternalClassDescription[this.classDescriptions.size()]);
}
// ********** package-accessible methods **********
/**
* Build the master list of class descriptions, keyed by name.
*/
abstract Map buildClassDescriptions(); // private-protected
/**
* Return the external class description that corresponds to the specified class.
* This is used by the various CLExternalObjects that need to resolve
* their references to other external class descriptions (e.g. CLExternalField has
* a type attribute).
*/
ExternalClassDescription getClassDescriptionFor(Class javaClass) {
// this should never be called before the types have been initialized by #getExternalClassDescriptions()...
if (javaClass.isArray()) {
return this.getArrayClassDescriptionFor(javaClass);
}
// assume that, if we were able to load the specified class with a
// URL classloader, it was discovered on the classpath and put in 'classDescriptions'
ExternalClassDescription classDescription = (ExternalClassDescription) this.classDescriptions.get(javaClass.getName());
if (classDescription == null) {
// there is something wrong with our classpath...
throw new IllegalStateException();
}
return classDescription;
}
/**
* Return the external [array] class description that corresponds
* to the specified [array] class.
*/
private ExternalClassDescription getArrayClassDescriptionFor(Class javaClass) {
synchronized (this.arrayClassDescriptions) {
ExternalClassDescription result = (ExternalClassDescription) this.arrayClassDescriptions.get(javaClass);
if (result == null) {
result = new CLExternalClassDescription(javaClass.getName(), this);
this.arrayClassDescriptions.put(javaClass, result);
}
return result;
}
}
/**
* Attempt to load the specified class. This is used by
* CLExternalClass when it is instantiated by
* CLExternalClassDescription.
*/
Class loadClass(String className) throws ExternalClassNotFoundException {
// lazy initialize to postpone the suffering until required
synchronized (this) {
if (this.classLoader == null) {
this.classLoader = this.buildClassLoader();
}
}
try {
return Class.forName(className, false, this.classLoader);
} catch (Throwable t) {
throw new ExternalClassNotFoundException(className, t);
}
}
/**
* Build the class loader.
*/
abstract ClassLoader buildClassLoader(); // private-protected
/**
* Helper method:
* Add all the "candidate" class descriptions found in the specified
* classpath to the specified collection of ExternalClassDescription objects.
*/
void addClassDescriptionsFromClasspathTo(Classpath classpath, Map map) { // private-protected
Classpath.Entry[] entries = classpath.getEntries();
int len = entries.length;
for (int i = 0; i < len; i++) {
this.addClassDescriptionsFromClasspathEntryTo(entries[i], map);
}
}
/**
* Add all the "candidate" class descriptions found in the specified
* classpath entry to the specified collection of ExternalClassDescriptions.
*/
private void addClassDescriptionsFromClasspathEntryTo(Classpath.Entry entry, Map map) {
for (Iterator stream = entry.classNamesStream(); stream.hasNext(); ) {
String name = (String) stream.next();
// do *not* replace entries - the first one found takes precedence
if ( ! map.containsKey(name)) {
map.put(name, new CLExternalClassDescription(name, entry.canonicalFileName(), this));
}
}
}
/**
* Helper method:
* Add all the repository's class descriptions to the specified map,
* keyed by the class names.
*/
void addClassDescriptionsTo(Map map) {
// lazy initialize to postpone the suffering until required
synchronized (this) {
if (this.classDescriptions == null) {
this.classDescriptions = this.buildClassDescriptions();
}
}
for (Iterator stream = this.classDescriptions.entrySet().iterator(); stream.hasNext(); ) {
Map.Entry entry = (Map.Entry) stream.next();
// do *not* replace entries - the first one found takes precedence
if ( ! map.containsKey(entry.getKey())) {
map.put(entry.getKey(), entry.getValue());
}
}
}
// ********** standard methods **********
public String toString() {
String moreInfo;
if (this.classDescriptions == null) {
moreInfo = "uninitialized";
} else {
moreInfo = String.valueOf(this.classDescriptions.size()) + " types";
}
return StringTools.buildToStringFor(this, moreInfo);
}
}