/*******************************************************************************
* 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.classfile;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.persistence.tools.workbench.mappingsmodel.spi.meta.ExternalClassDescription;
import org.eclipse.persistence.tools.workbench.mappingsmodel.spi.meta.ExternalClassRepository;
import org.eclipse.persistence.tools.workbench.utility.ClassTools;
import org.eclipse.persistence.tools.workbench.utility.Classpath;
import org.eclipse.persistence.tools.workbench.utility.string.StringTools;
/**
* This external class repository maintains a collection of class descriptions that
* can generate external classes from Java class files.
*/
final class CFExternalClassRepository
implements ExternalClassRepository
{
/**
* Cache the "project" classpath with all its entries
* fully qualified and duplicates stripped out.
*/
private final Classpath classpath;
/**
* The external class descriptions for this repository.
*/
private ExternalClassDescription[] classDescriptions; // pseudo-final
/**
* The "default" external class descriptions for this repository - keyed by name.
* The "default" class descriptions are the class descriptions returned when
* you request class descriptions by name.
* Typically we will return the first class description found on the
* classpath with the requested name. This should not be a
* problem since the important class descriptions are those that end up building
* external *classes* and those should be chosen explicitly, not
* requested by name.
*/
private Map defaultClassDescriptions; // pseudo-final
/**
* The "stub", non-array external class descriptions for this repository - keyed by name.
* This will hold the non-array class descriptions that
* are referenced by the external *classes* that were
* not included on the classpath.
* This is built as the class descriptions are requested.
*/
private final Map stubClassDescriptions;
/**
* The external "array" class descriptions for this repository - keyed by name.
* This is built as the class descriptions are requested.
*/
private final Map arrayClassDescriptions;
// ********** constructors **********
/**
* Construct a class repository for the specified "project" classpath.
*/
CFExternalClassRepository(File[] classpath) {
super();
this.stubClassDescriptions = new HashMap();
this.arrayClassDescriptions = new HashMap();
this.classpath = new Classpath(this.fileNames(classpath)).compressed();
}
/**
* Return an array of the names of the specified files.
*/
private String[] fileNames(File[] files) {
int len = files.length;
String[] fileNames = new String[len];
for (int i = 0; i < len; i++) {
fileNames[i] = files[i].getAbsolutePath();
}
return fileNames;
}
// ********** ExternalClassRepository implementation **********
/**
* @see org.eclipse.persistence.tools.workbench.mappingsmodel.spi.meta.ExternalClassRepository#getClassDescription(String)
*/
public ExternalClassDescription getClassDescription(String typeName) {
// lazy initialize to postpone the suffering until required
synchronized (this) {
if (this.classDescriptions == null) {
this.initializeClassDescriptions();
}
}
return (ExternalClassDescription) this.defaultClassDescriptions.get(typeName);
}
/**
* @see org.eclipse.persistence.tools.workbench.mappingsmodel.spi.meta.ExternalClassRepository#getClassDescriptions()
*/
public ExternalClassDescription[] getClassDescriptions() {
// lazy initialize to postpone the suffering until required
synchronized (this) {
if (this.classDescriptions == null) {
this.initializeClassDescriptions();
}
}
return this.classDescriptions;
}
private static final int STARTING_SIZE = 20000; // let's start large
/**
* Build the master lists of class descriptions.
*/
private void initializeClassDescriptions() {
List list = new ArrayList(STARTING_SIZE);
this.defaultClassDescriptions = new HashMap(STARTING_SIZE);
// first add all the primitives, since they do not show up on any classpath
for (int i = PRIMITIVE_EXTERNAL_CLASS_DESCRIPTIONS.length; i-- > 0; ) {
ExternalClassDescription classDescription = PRIMITIVE_EXTERNAL_CLASS_DESCRIPTIONS[i];
list.add(classDescription);
this.defaultClassDescriptions.put(classDescription.getName(), classDescription);
}
Classpath.Entry[] entries = this.classpath.getEntries();
int len = entries.length;
for (int i = 0; i < len; i++) {
this.addClassDescriptionsFromClasspathEntryTo(entries[i], list, this.defaultClassDescriptions);
}
this.classDescriptions = (ExternalClassDescription[]) list.toArray(new ExternalClassDescription[list.size()]);
}
/**
* Add all the "candidate" class descriptions found in the specified
* classpath entry to the collections of ExternalClassDescriptions.
*/
private void addClassDescriptionsFromClasspathEntryTo(Classpath.Entry entry, List list, Map map) {
for (Iterator stream = entry.classNamesStream(); stream.hasNext(); ) {
String name = (String) stream.next();
ExternalClassDescription classDescription = new CFExternalClassDescription(name, entry.canonicalFileName(), this);
list.add(classDescription);
// do *not* replace entries - the first one found takes precedence
if ( ! map.containsKey(name)) {
map.put(name, classDescription);
}
}
}
// ********** package-accessible methods **********
/**
* Return the external class description that corresponds to the specified class.
* This is used by the various CFExternalObjects that need to resolve
* their references to other external class descriptions (e.g. CFExternalField has
* a type attribute).
*/
ExternalClassDescription getClassDescriptionNamed(String className) {
// this should never be called before the types have been initialized by #getExternalClassDescriptions()...
if (ClassTools.classNamedIsArray(className)) {
return this.getArrayClassDescriptionNamed(className);
}
// if the requested class description was not on the classpath, put a "shell" class description in 'defaultClassDescriptions'
ExternalClassDescription classDescription = (ExternalClassDescription) this.defaultClassDescriptions.get(className);
if (classDescription == null) {
classDescription = this.getStubClassDescriptionNamed(className);
}
return classDescription;
}
/**
* Return the "stub" external class description that corresponds
* to the specified class.
*/
private ExternalClassDescription getStubClassDescriptionNamed(String className) {
synchronized (this.stubClassDescriptions) {
ExternalClassDescription stubClassDescription = (ExternalClassDescription) this.stubClassDescriptions.get(className);
if (stubClassDescription == null) {
stubClassDescription = new CFExternalClassDescription(className, this);
this.stubClassDescriptions.put(className, stubClassDescription);
}
return stubClassDescription;
}
}
/**
* Return the external [array] class description that corresponds
* to the specified [array] class.
*/
private ExternalClassDescription getArrayClassDescriptionNamed(String className) {
synchronized (this.arrayClassDescriptions) {
ExternalClassDescription arrayClassDescription = (ExternalClassDescription) this.arrayClassDescriptions.get(className);
if (arrayClassDescription == null) {
arrayClassDescription = new CFExternalClassDescription(className, this);
this.arrayClassDescriptions.put(className, arrayClassDescription);
}
return arrayClassDescription;
}
}
// ********** standard methods **********
public String toString() {
String moreInfo;
if (this.classDescriptions == null) {
moreInfo = "uninitialized";
} else {
moreInfo = String.valueOf(this.classDescriptions.length) + " types";
}
return StringTools.buildToStringFor(this, moreInfo);
}
}