/*******************************************************************************
* 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.meta;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import org.eclipse.persistence.tools.workbench.mappingsmodel.MWModel;
import org.eclipse.persistence.tools.workbench.mappingsmodel.ProjectSubFileComponentContainer;
import org.eclipse.persistence.tools.workbench.mappingsmodel.descriptor.MWDescriptor;
import org.eclipse.persistence.tools.workbench.mappingsmodel.mapping.MWMapping;
import org.eclipse.persistence.tools.workbench.mappingsmodel.project.MWProject;
import org.eclipse.persistence.tools.workbench.mappingsmodel.spi.meta.ClassDescription;
import org.eclipse.persistence.tools.workbench.mappingsmodel.spi.meta.ExternalClass;
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.mappingsmodel.spi.meta.ExternalClassRepositoryFactory;
import org.eclipse.persistence.tools.workbench.utility.ClassTools;
import org.eclipse.persistence.tools.workbench.utility.Classpath;
import org.eclipse.persistence.tools.workbench.utility.CollectionTools;
import org.eclipse.persistence.tools.workbench.utility.iterators.ArrayIterator;
import org.eclipse.persistence.tools.workbench.utility.iterators.CloneIterator;
import org.eclipse.persistence.tools.workbench.utility.iterators.CloneListIterator;
import org.eclipse.persistence.tools.workbench.utility.iterators.CompositeIterator;
import org.eclipse.persistence.tools.workbench.utility.iterators.FilteringIterator;
import org.eclipse.persistence.tools.workbench.utility.iterators.TransformationIterator;
import org.eclipse.persistence.tools.workbench.utility.iterators.TreeIterator;
import org.eclipse.persistence.tools.workbench.utility.node.Node;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.descriptors.DescriptorEvent;
import org.eclipse.persistence.oxm.XMLDescriptor;
import org.eclipse.persistence.oxm.mappings.XMLCompositeDirectCollectionMapping;
import org.eclipse.persistence.oxm.mappings.XMLTransformationMapping;
import org.eclipse.persistence.sessions.Record;
import org.eclipse.persistence.sessions.Session;
/**
* This is the factory and repository for all the class "descriptions"
* (MWClasses) that represent the user's object model.
*
* This "internal" class repository manages all the MWClasses.
* It uses an "external" class repository to build the MWClasses
* from external metadata, which are typically derived from Java
* Class objects or byte codes.
*
* There are several varieties of MWClasses:
* - "core" classes
* for performance reasons, a "core" class (i.e. a JDK or TopLink
* classes) is initially only partially populated with state related
* to its class declaration (superclass, interfaces, etc.); the
* remaining state is lazily populated on demand
* - "stub" classes
* a user class that is only referenced by other user classes is not
* populated beyond its name (and whether it might be an interface)
* - "fully populated" classes
* a user class that is mapped, or whose fields or methods are referenced
* by descriptors, is fully populated with all its state; this is the only type
* of class that is written out to an XML file
*
* To avoid confusion between Java Classes and MWClasses,
* we refer to MWClasses as "types" whenever possible and
* to Java Classes as "classes".
*
* NB: We synchronized 'types' whenever types are being added or removed.
* This is because multiple threads can fault in new types. :-( ~bjv
*/
public final class MWClassRepository
extends MWModel
implements ProjectSubFileComponentContainer
{
/**
* The MWClasses, which includes "user" types, "stub" types,
* and "core" types, keyed by name. We need to carefully
* synchronize access to this map since we will fault in types
* whenever they are referenced. This means any number of
* methods can cause an addition to this map.
*/
private Map types;
// 'types' is NOT a public property, but 'userTypes' is
/**
* The names of the types contained in 'types', above,
* keyed by the name converted to lower-case.
* This must be kept in synch with 'types'.
* @see #addType(String, MWClass)
* @see #removeTypeNamed(String)
*/
private Map typeNames;
/**
* this holds only the current set of "user" types and must
* be maintained by tracking changes to the types themselves (yuck);
* @see #typeChanged(MWClass)
* @see MWClass#aspectChanged(String)
*/
private Set userTypes;
public static final String USER_TYPES_COLLECTION = "userTypes";
/**
* these user type names are read in by TopLink and are then
* used and managed by the IOManager;
* DO NOT use them for anything else ~bjv
*/
private Collection userTypeNames;
private static final String USER_TYPE_NAMES_COLLECTION = "userTypeNames";
/**
* the "external" class repository that supplies the
* "external" types/classes used to build MWClasses
*/
private volatile ExternalClassRepository externalClassRepository;
/**
* classpath used by the "external" class repository, if necessary, to
* search for the "external" classes used to build MWClasses;
* it is a list of strings
*/
private List classpathEntries;
public static final String CLASSPATH_ENTRIES_LIST = "classpathEntries";
/** these are the keys used to find the classpath entries for the "core" classes */
private static final Class[] CORE_KEYS =
new Class[] {
java.lang.Object.class, // rt.jar
//some custom jvms have the classes divided between more than one jar which can cause problems
//associated with bug #222471
java.util.List.class,
java.util.Map.class,
java.util.Collection.class,
java.util.Set.class,
org.eclipse.persistence.indirection.ValueHolderInterface.class // eclipselink.jar
};
/** the "core" classes: the names of the MWClasses that are never written out */
private static Set coreClassNames;
/**
* the "core" classes: the names of the MWClasses that are never written out,
* keyed by the name converted to lowercase
*/
private static Map coreClassNamesLowerCase;
// queried reflectively by I/O Manager
private static final String SUB_DIRECTORY_NAME = "classes";
//not peristed
private boolean persistLastRefresh;
public static final String PERSIST_LAST_REFRESH_PREFERENCE = "last refresh";
public static final boolean PERSIST_LAST_REFRESH_PREFERENCE_DEFAULT = true;
// ********** constructors **********
/**
* default constructor - for TopLink use only
*/
private MWClassRepository() {
super();
}
/**
* public constructor:
* this is the starting point for all the classes in the meta package...
*/
public MWClassRepository(MWProject project) {
super(project);
}
// ********** initialization **********
/**
* initialize transient state
*/
protected void initialize() {
super.initialize();
}
/**
* initialize persistent state
*/
protected void initialize(Node parent) {
super.initialize(parent);
this.types = new Hashtable();
this.typeNames = new Hashtable();
this.userTypes = Collections.synchronizedSet(new HashSet());
this.classpathEntries = new Vector();
this.userTypeNames = new HashSet();
this.persistLastRefresh = true;
}
// ********** accessors **********
/**
* PRIVATE - called by I/O Manager (ProjectReader);
* 'types' should only hold "user" types after it is initialized by
* the I/O Manager
*/
private void setTypes(Collection types) {
this.types = new Hashtable(types.size());
this.typeNames = new Hashtable(types.size());
for (Iterator stream = types.iterator(); stream.hasNext(); ) {
this.addType((MWClass) stream.next());
}
this.userTypes = Collections.synchronizedSet(new HashSet(types));
}
private void addType(MWClass type) {
this.addType(type.getName(), type);
}
/**
* keep 'types' and 'typeNames' in synch
*/
private void addType(String typeName, MWClass type) {
// don't add the type to 'userTypes' - it will be added when the type becomes a "non-stub" type
Object previousType = this.types.put(typeName, type);
if (previousType != null) {
this.types.put(typeName, previousType); // restore 'types'
throw new IllegalArgumentException("duplicate types: " + previousType + " vs. " + type);
}
Object previousTypeName = this.typeNames.put(typeName.toLowerCase(), typeName);
if (previousTypeName != null) {
this.types.remove(typeName); // restore 'types'
this.typeNames.put(typeName.toLowerCase(), previousTypeName); // restore 'typeNames'
throw new IllegalArgumentException("type names cannot differ only by case: " + previousTypeName + " vs. " + typeName);
}
}
/**
* keep 'types', 'typeNames', and 'userTypes' in synch
*/
private void removeType(MWClass type) {
this.removeTypeNamed(type.getName());
// 'userTypes' may or may not contain the type - it doesn't matter
this.removeUserType(type);
}
/**
* keep 'types', and 'typeNames' in synch; but leave 'userTypes' unchanged(!)
*/
private MWClass removeTypeNamed(String typeName) {
Object previousTypeName = this.typeNames.remove(typeName.toLowerCase());
if (previousTypeName == null) {
throw new IllegalArgumentException("missing type name: " + typeName);
}
if ( ! previousTypeName.equals(typeName)) {
this.typeNames.put(typeName.toLowerCase(), previousTypeName); // restore 'typeNames'???
throw new IllegalArgumentException("inconsistent type names: " + previousTypeName + " vs. " + typeName);
}
MWClass previousType = (MWClass) this.types.remove(typeName);
if (previousType == null) {
this.typeNames.put(typeName.toLowerCase(), previousTypeName); // restore 'typeNames'???
throw new IllegalArgumentException("missing type: " + typeName);
}
return previousType;
}
/**
* Return the "user" types; i.e. all the types that are neither
* "stub" types nor "core" types.
*/
public Iterator userTypes() {
return new CloneIterator(this.userTypes);
}
public int userTypesSize() {
return this.userTypes.size();
}
private void addUserType(MWClass userType) {
this.addItemToCollection(userType, this.userTypes, USER_TYPES_COLLECTION);
}
private void removeUserType(MWClass userType) {
this.removeItemFromCollection(userType, this.userTypes, USER_TYPES_COLLECTION);
}
/**
* PRIVATE - no one should need direct access to the external
* class repository;
* lazy initialize so we don't have to propagate exceptions any
* more than necessary
*/
private ExternalClassRepository getExternalClassRepository() {
if (this.externalClassRepository == null) {
this.externalClassRepository = this.buildExternalClassRepository();
}
return this.externalClassRepository;
}
private ExternalClassRepository buildExternalClassRepository() {
return this.externalClassRepositoryFactory().buildClassRepository(this.buildExternalClassRepositoryClasspath());
}
/** NOTE: Classpath entries are Strings */
public ListIterator classpathEntries() {
return new CloneListIterator(this.classpathEntries);
}
public int classpathEntriesSize() {
return this.classpathEntries.size();
}
public String getClasspathEntry(int index) {
return (String) this.classpathEntries.get(index);
}
public void addClasspathEntry(int index, String entry) {
this.addItemToList(index, entry, this.classpathEntries, CLASSPATH_ENTRIES_LIST);
this.externalClassRepository = null;
}
public void addClasspathEntry(String entry) {
this.addClasspathEntry(this.classpathEntriesSize(), entry);
}
public void addClasspathEntries(int index, List entries) {
this.addItemsToList(index, entries, this.classpathEntries, CLASSPATH_ENTRIES_LIST);
this.externalClassRepository = null;
}
public void addClasspathEntries(List entries) {
this.addClasspathEntries(this.classpathEntriesSize(), entries);
}
public void addClasspathEntries(ListIterator entries) {
this.addClasspathEntries(CollectionTools.list(entries));
}
public String removeClasspathEntry(int index) {
String result = (String) this.removeItemFromList(index, this.classpathEntries, CLASSPATH_ENTRIES_LIST);
this.externalClassRepository = null;
return result;
}
public List removeClasspathEntries(int index, int length) {
List result = this.removeItemsFromList(index, length, this.classpathEntries, CLASSPATH_ENTRIES_LIST);
this.externalClassRepository = null;
return result;
}
public String replaceClasspathEntry(int index, String newEntry) {
String result = (String) this.setItemInList(index, newEntry, this.classpathEntries, CLASSPATH_ENTRIES_LIST);
this.externalClassRepository = null;
return result;
}
// ********** queries **********
/**
* the external class repository factory is supplied by client code
*/
private ExternalClassRepositoryFactory externalClassRepositoryFactory() {
return this.getProject().getSPIManager().getExternalClassRepositoryFactory();
}
/**
* return an iterator on all the external class descriptions
* available in the external class repository
*/
public Iterator externalClassDescriptions() {
return new ArrayIterator(this.getExternalClassRepository().getClassDescriptions());
}
/**
* return an iterator on all the external "reference" class
* descriptions available in the external class repository;
* this will *not* include void or the primitives
*/
public Iterator externalReferenceClassDescriptions() {
return this.referenceTypes(this.externalClassDescriptions());
}
/**
* return the first encountered external class description with the specified name,
* as defined by the external class repository implementation;
* the external class repository may contain more than
* one external class description with the specified name...
*/
private ExternalClassDescription externalClassDescriptionNamed(String name) {
return this.getExternalClassRepository().getClassDescription(name);
}
/**
* return an iterator on all the external class descriptions
* available in the external class repository
* with the specified name
*/
Iterator externalClassDescriptionsNamed(final String name) {
return new FilteringIterator(this.externalClassDescriptions()) {
public boolean accept(Object next) {
return ((ExternalClassDescription) next).getName().equals(name);
}
public String toString() {
return "MWClassRepository.externalClassDescriptionsNamed(String)";
}
};
}
/**
* return an iterator on all the external class descriptions
* available in the external class repository
* combined with all the internal types in the repository
*/
public Iterator combinedTypes() {
return new CompositeIterator(new CloneIterator(this.userTypes), this.externalClassDescriptions());
}
/**
* return an iterator on all the external "reference" types
* available in the external class repository
* combined with all the internal "reference" types in
* the repository;
* this will *not* include void or the primitives
*/
public Iterator combinedReferenceTypes() {
return this.referenceTypes(this.combinedTypes());
}
/**
* return an iterator on all the external class descriptions
* available in the external class repository
* combined with all the internal types in
* the repository, eliminating duplicates
*/
public Iterator uniqueCombinedTypes() {
return this.uniqueTypes(this.combinedTypes());
}
/**
* return an iterator on all the external "reference" types
* available in the external class repository
* combined with all the internal "reference" types in
* the repository, eliminating duplicates;
* this will *not* include void or the primitives
*/
public Iterator uniqueCombinedReferenceTypes() {
return this.uniqueTypes(this.combinedReferenceTypes());
}
/**
* filter the specified iterator of class descriptions to
* return only a unique set of types, based on the type names
*/
private Iterator uniqueTypes(Iterator originalTypes) {
return new FilteringIterator(originalTypes) {
private Set usedNames = new HashSet(10000); // this will be big...
protected boolean accept(Object next) {
// @see java.util.Set#add(Object)
return this.usedNames.add(((ClassDescription) next).getName());
}
public String toString() {
return "MWClassRepository.uniqueTypes(Iterator)";
}
};
}
/**
* filter the specified iterator of class descriptions to
* return only "reference" types;
* this will *not* include void or the primitives (int, char, etc.)
*/
private Iterator referenceTypes(Iterator originalTypes) {
return new FilteringIterator(originalTypes) {
protected boolean accept(Object next) {
return ! MWClass.nonReferenceClassNamesContains(((ClassDescription) next).getName());
}
public String toString() {
return "MWClassRepository.referenceTypes(Iterator)";
}
};
}
/**
* return the directory used to convert relative
* classpath entries to fully qualified files
*/
File classpathBaseDirectory() {
return this.getProject().getSaveDirectory();
}
/**
* return the classpath with the entries converted to
* fully qualified files (any relative entries will be
* resolved relative to the project save directory)
*/
private File[] buildExternalClassRepositoryClasspath() {
List<String> coreFiles = buildCoreClassLocations();
List files = new ArrayList(this.classpathEntriesSize() + coreFiles.size());
CollectionTools.addAll(files, this.fullyQualifiedClasspathFiles());
ListIterator<String> coreFileIter = coreFiles.listIterator();
// hard-code the "core" classes at the back of the classpath
// so they can be overridden by the client
while (coreFileIter.hasNext()) {
files.add(new File(coreFileIter.next()));
}
return (File[]) files.toArray(new File[files.size()]);
}
/**
* return the classpath with the entries converted to
* fully qualified files (any relative entries will be
* resolved relative to the project save directory)
*/
private Iterator fullyQualifiedClasspathFiles() {
return new TransformationIterator(this.classpathEntries()) {
protected Object transform(Object next) {
File file = new File((String) next);
if ( ! file.isAbsolute()) {
file = new File(MWClassRepository.this.classpathBaseDirectory(), file.getPath());
}
return file;
}
public String toString() {
return "MWClassRepository.fullyQualifiedClasspathFiles()";
}
};
}
/**
* Return the entries with path resolved to project save directory.
*/
public Iterator fullyQualifiedClasspathEntries() {
return new TransformationIterator(this.fullyQualifiedClasspathFiles()) {
protected Object transform(Object next) {
return ((File) next).getAbsolutePath();
}
public String toString() {
return "MWClassRepository.fullyQualifiedClasspathEntries()";
}
};
}
/**
* return the immediate [loaded] subclasses of the specified type
*/
Iterator subclassesOf(MWClass type) {
Collection subclasses = new ArrayList();
synchronized (this.types) {
for (Iterator stream = this.types.values().iterator(); stream.hasNext(); ) {
MWClass next = (MWClass) stream.next();
if (next.getSuperclass() == type) {
subclasses.add(next);
}
}
}
return subclasses.iterator();
}
/**
* return the immediate [loaded] subclasses of the class,
* all their [loaded] subclasses, and so on
*/
public Iterator allSubclassesOf(MWClass type) {
return new TreeIterator(type) {
protected Iterator children(Object next) {
return ((MWClass) next).subclasses();
}
public String toString() {
return "MWClassRepository.allSubclassesOf(MWClass)";
}
};
}
/**
* return the specified type if it has been loaded;
* return null if the specified type is not loaded
* (or has been "garbage-collected")
*/
public MWClass typeNamedIgnoreCase(String typeName) {
synchronized (this.types) {
return this.typeNamedIgnoreCase2(typeName);
}
}
/**
* unsynchronized version of #typeNamedIgnoreCase(String)
*/
private MWClass typeNamedIgnoreCase2(String typeName) {
typeName = (String) this.typeNames.get(typeName.toLowerCase());
if (typeName == null) {
return null;
}
MWClass type = (MWClass) this.types.get(typeName);
return this.typeIsGarbage(type) ? null : type;
}
/**
* return whether the specified type is "garbage";
* i.e. there are no longer any references to it;
* if the type is garbage, it will be removed as a side-effect
*/
private boolean typeIsGarbage(MWClass type) {
for (Iterator stream = this.getProject().branchReferences(); stream.hasNext(); ) {
Reference ref = (Reference) stream.next();
if (ref.getTarget().isDescendantOf(type)) {
// something is still holding on to the type or one of
// its descendants, so we can't remove it yet
return false;
}
}
this.removeType(type);
return true;
}
/**
* If we already have the specified type, return it;
* otherwise, return a newly-built stub (i.e. we will
* never return null from this method).
*
* We will *not* attempt to build up a fully-populated type;
* but we will *partially* build up certain, core types (e.g.
* Collection, ValueHolderInterface); and some of these
* core types will always be fully-populated, whether you
* like it or not (e.g. Object).
*
* Typically, this is the best method to call when you just need
* a class (as opposed to an attribute or method). If you need
* a fully-populated type, call this method to get the type,
* check whether the type is a stub, and, if it is a stub, refresh it.
*
* You may NOT always want to refresh a type, since that might
* drop any changes entered manually by the user since the
* last refresh; but it should be OK to refresh a stub.
*
* If you need to refresh a type from a new "external" definition
* (e.g. a freshly compiled set of bytecodes),
* call the method #refreshExternalClassDescriptions() before refreshing
* the type. This will cause the external class repository to be
* rebuilt and should allow the new "external" class definition
* to be used during the refresh.
*
* This method should only be called by MWModel or MWHandle; other
* classes should use MWModel#typeNamed(String) or
* MWHandle#typeNamed(String).
*/
public MWClass typeNamedInternal(String typeName) {
synchronized (this.types) {
return this.typeNamedInternal2(typeName);
}
}
/**
* unsynchronized version of #typeNamedInternal(String)
*/
private MWClass typeNamedInternal2(String typeName) {
if (typeName == null) {
throw new NullPointerException();
}
typeName = typeName.trim();
MWClass type = (MWClass) this.types.get(typeName);
if (type != null) {
return type;
}
boolean coreType = this.checkTypeName(typeName);
type = new MWClass(this, typeName, coreType);
this.addType(typeName, type);
type.initializeNameDependentState();
// core types must be, at least, partially populated...
if (coreType) {
this.buildCoreType(type);
}
return type;
}
/**
* validate the specified type name and return whether
* it is the name of a "core" type;
* the return value isn't really consistent with the purpose of
* the method, but it provides us with a slight performance
* improvement
*/
private boolean checkTypeName(String typeName) {
if (typeName.length() == 0) {
throw new IllegalArgumentException("empty type name");
}
if (ClassTools.classNamedIsArray(typeName)) {
throw new IllegalArgumentException("use MWTypeDeclaration for array types");
}
if (coreClassNamesContains(typeName)) {
return true;
}
// "java.lang.Object" is OK, but "Java.Lang.OBJECT" is not
String collision = coreClassNameIgnoreCase(typeName);
if (collision != null) {
throw new IllegalArgumentException("case-insensitive name collision with \"core\" type: " + collision);
}
return false;
}
/**
* return the primitive types (e.g. int, char);
* does *not* include void
*/
public Iterator primitiveTypes() {
return new TransformationIterator(MWClass.primitiveClassNames()) {
protected Object transform(Object next) {
// the primitives are always fully populated
return MWClassRepository.this.typeNamedInternal((String) next);
}
public String toString() {
return "MWClassRepository.primitiveTypes()";
}
};
}
/**
* return the type corresponding to the void keyword
*/
public MWClass voidType() {
// void is always fully populated
return this.typeNamedInternal(MWClass.voidClassName());
}
/**
* Look for the specified resource on the project classpath.
*/
public URL findResource(String resourceName) {
Classpath cp;
synchronized (this.classpathEntries) {
cp = new Classpath(this.classpathEntries);
}
return new URLClassLoader(cp.urls()).findResource(resourceName);
}
// ********** behavior **********
/**
* containment hierarchy
*/
protected void addChildrenTo(List children) {
super.addChildrenTo(children);
synchronized (this.types) { children.addAll(this.types.values()); }
}
/**
* One of the types has changed; determine whether the change
* affects our "user types" collection.
*/
void typeChanged(MWClass type) {
if (type.isCoreType()) {
return;
}
if (type.isStub()) {
this.removeUserType(type);
} else {
this.addUserType(type);
}
}
/**
* a type has been renamed - validate the new name and rehash our maps
*/
void typeRenamed(String oldName, String newName) {
if (this.checkTypeName(newName)) {
throw new IllegalArgumentException("type cannot be renamed to a \"core\" type");
}
MWClass type;
synchronized (this.types) {
// leave the type in 'userTypes' if it's there
type = this.removeTypeNamed(oldName);
this.addType(newName, type);
}
if (this.userTypes.contains(type)) {
// if a user type has been renamed, we need to fire an "internal"
// change event so the repository is marked dirty
this.fireCollectionChanged(USER_TYPE_NAMES_COLLECTION);
}
}
/**
* refresh the specified type using a default refresh policy
* @see #refresh(MWClass, MWClassRefreshPolicy)
*/
void refreshType(MWClass type) throws ExternalClassNotFoundException {
this.refreshType(type, DefaultMWClassRefreshPolicy.instance());
}
/**
* refresh the specified type from the current set of external class descriptions;
* if you would like to force the type to be refreshed from a newly-built
* set of external class descriptions, call #refreshExternalClassDescriptions() first;
* the type will be refreshed with the "default" external class description returned by
* the external class repository
* @see ExternalClassRepository#getExternalClassDescription(String)
*/
void refreshType(MWClass type, MWClassRefreshPolicy refreshPolicy) throws ExternalClassNotFoundException {
ExternalClassDescription exClassDescription = this.externalClassDescriptionNamed(type.getName());
if (exClassDescription == null) {
throw new ExternalClassNotFoundException(type.getName());
}
type.refresh(exClassDescription.getExternalClass(), refreshPolicy);
}
/**
* refresh the specified type's members from the current set of external class descriptions;
* if you would like to force the type to be refreshed from a newly-built
* set of external class descriptions, call #refreshExternalClassDescriptions() first;
* the type's members will be refreshed with the "default" external class description returned by
* the external class repository;
* this method is used to finish the "lazy refresh" of a "core" type
* @see ExternalClassRepository#getExternalClassDescription(String)
*/
void refreshTypeMembers(MWClass type, MWClassRefreshPolicy refreshPolicy) throws ExternalClassNotFoundException {
ExternalClassDescription exClassDescription = this.externalClassDescriptionNamed(type.getName());
if (exClassDescription == null) {
throw new ExternalClassNotFoundException(type.getName());
}
type.refreshMembers(exClassDescription.getExternalClass(), refreshPolicy);
}
/**
* refresh the collection of external class descriptions by forcing
* a rebuild of the external class repository; the next
* call to #externalClassDescriptions() will return an iterator on
* the refreshed collection
*/
public void refreshExternalClassDescriptions() {
this.externalClassRepository = null;
}
/**
* refresh the specified type with the "default" external class description;
* throw an exception if we can't load the corresponding metadata
*/
public void refreshTypeNamed(String typeName) throws ExternalClassNotFoundException {
this.typeNamedInternal(typeName).refresh();
}
/**
* refresh the type corresponding to the specified external class description;
* throw an exception if we can't load the corresponding metadata
*/
public void refreshTypeFor(ExternalClassDescription externalClassDescription) throws ExternalClassNotFoundException {
this.typeNamedInternal(externalClassDescription.getName()).refresh(externalClassDescription.getExternalClass());
}
/**
* refresh the types corresponding to the specified external class descriptions;
* notify the specified listener for each corresponding
* chunk of metadata we can't load
*/
public void refreshTypesFor(Iterator externalClassDescriptions, ExternalClassLoadFailureListener listener) {
while (externalClassDescriptions.hasNext()) {
ExternalClassDescription externalClassDescription = (ExternalClassDescription) externalClassDescriptions.next();
try {
this.refreshTypeFor(externalClassDescription);
} catch (ExternalClassNotFoundException ex) {
listener.externalClassLoadFailure(new ExternalClassLoadFailureEvent(this, externalClassDescription.getName(), ex));
}
}
}
/**
* refresh the types corresponding to the specified external class descriptions;
* return the failures
*/
public ExternalClassLoadFailureContainer refreshTypesFor(Iterator externalClassDescriptions) {
ExternalClassLoadFailureContainer listener = new ExternalClassLoadFailureContainer();
this.refreshTypesFor(externalClassDescriptions, listener);
return listener;
}
/**
* refresh the specified MWClass types;
* notify the specified listener for each corresponding
* chunk of metadata we can't load
*/
public void refreshTypes(Iterator refreshTypes, ExternalClassLoadFailureListener listener) {
while (refreshTypes.hasNext()) {
MWClass type = (MWClass) refreshTypes.next();
try {
this.refreshType(type);
} catch (ExternalClassNotFoundException ecnfe) {
listener.externalClassLoadFailure(new ExternalClassLoadFailureEvent(this, type.getName(), ecnfe));
}
}
}
/**
* refresh the specified MWClass types;
* return the failures
*/
public ExternalClassLoadFailureContainer refreshTypes(Iterator refreshTypes) {
ExternalClassLoadFailureContainer listener = new ExternalClassLoadFailureContainer();
this.refreshTypes(refreshTypes, listener);
return listener;
}
/**
* core types are partially-populated
*/
private void buildCoreType(MWClass coreType) {
try {
// for now, assume that *any* version of the external class description is OK; so just take the default one
ExternalClassDescription externalClassDescription = this.externalClassDescriptionNamed(coreType.getName());
ExternalClass externalClass = externalClassDescription.getExternalClass();
coreType.refreshDeclaration(externalClass);
} catch (ExternalClassNotFoundException ex) {
// if we can't load a "core" type, we are in serious trouble...
throw new RuntimeException(coreType.getName(), ex);
}
}
/**
* notify the branch of classes under the specified type that
* their superclasses have changed
*/
void hierarchyChanged(MWClass type) {
for (Iterator stream = this.allSubclassesOf(type); stream.hasNext(); ) {
((MWClass) stream.next()).superclassesChanged();
}
}
/**
* performance tuning: override this method and assume
* the repository's descendants have NO references (handles)
* to any models other than other descendants of the repository
*/
public void nodeRemoved(Node node) {
if (node.isDescendantOf(this)) {
super.nodeRemoved(node);
}
}
/**
* performance tuning: override this method and assume
* the repository's descendants have NO references (handles)
* to any models other than other descendants of the repository
*/
public void nodeRenamed(Node node) {
if (node.isDescendantOf(this)) {
super.nodeRenamed(node);
// we handle a renamed type directly in #typeRenamed(String, String)
}
}
/**
* performance tuning: ignore this method - assume there are no
* references to mappings in the class repository or its descendants
*/
public void mappingReplaced(MWMapping oldMapping, MWMapping newMapping) {
// do nothing
}
/**
* performance tuning: ignore this method - assume there are no
* references to descriptors in the database or its descendants
*/
public void descriptorReplaced(MWDescriptor oldDescriptor, MWDescriptor newDescriptor) {
// do nothing
}
/**
* performance tuning: ignore this method - assume there are no
* references to mappings in the class repository or its descendants
*/
public void descriptorUnmapped(Collection mappings) {
// do nothing
}
// ********** displaying and printing **********
public void toString(StringBuffer sb) {
sb.append(this.types.size());
sb.append(" types/");
sb.append(this.userTypes.size());
sb.append(" user types");
}
// ********** SubComponentContainer implementation **********
public Iterator projectSubFileComponents() {
return this.userTypes();
}
public void setProjectSubFileComponents(Collection subComponents) {
this.setTypes(subComponents);
}
public Iterator originalProjectSubFileComponentNames() {
return this.userTypeNames.iterator();
}
public void setOriginalProjectSubFileComponentNames(Collection originalSubComponentNames) {
this.userTypeNames = originalSubComponentNames;
}
public boolean hasChangedMainProjectSaveFile() {
if (this.isDirty()) {
// the repository itself is dirty
return true;
}
for (Iterator stream = this.children(); stream.hasNext(); ) {
if (this.childHasChangedTheProjectSaveFile(stream.next())) {
return true;
}
}
// the types might be dirty
return false;
}
/**
* return whether the specified child of the repository is dirty AND
* is written to the .mwp file
*/
private boolean childHasChangedTheProjectSaveFile(Object child) {
if (this.types.containsValue(child)) {
// types are written to separate files
return false;
}
// the child is NOT a type,
// so all of its state is written to the .mwp file
return ((Node) child).isDirtyBranch();
}
// ********** TopLink methods **********
public static XMLDescriptor buildDescriptor() {
XMLDescriptor descriptor = new XMLDescriptor();
descriptor.setJavaClass(MWClassRepository.class);
XMLCompositeDirectCollectionMapping classpathMapping = new XMLCompositeDirectCollectionMapping();
classpathMapping.setAttributeName("classpathEntries");
classpathMapping.setSetMethodName("setClasspathEntriesForTopLink");
classpathMapping.setGetMethodName("getClasspathEntriesForTopLink");
classpathMapping.setXPath("classpath-entries/entry/text()");
descriptor.addMapping(classpathMapping);
XMLCompositeDirectCollectionMapping userTypeNamesMapping = new XMLCompositeDirectCollectionMapping();
userTypeNamesMapping.setAttributeName("userTypeNames");
userTypeNamesMapping.setSetMethodName("setUserTypeNamesForTopLink");
userTypeNamesMapping.setGetMethodName("getUserTypeNamesForTopLink");
userTypeNamesMapping.useCollectionClass(HashSet.class);
userTypeNamesMapping.setXPath("user-type-names/name/text()");
descriptor.addMapping(userTypeNamesMapping);
return descriptor;
}
/**
* sort and return only the names of the "user" types
* (non-core and non-stub types)
*/
private Collection getUserTypeNamesForTopLink() {
List names = new ArrayList(this.userTypes.size());
synchronized (this.userTypes) {
for (Iterator stream = this.userTypes.iterator(); stream.hasNext(); ) {
names.add(((MWClass) stream.next()).getName());
}
}
return CollectionTools.sort(names, Collator.getInstance());
}
/**
* TopLink sets this value, which is then used by the
* ProjectIOManager to read in the actual types
*/
private void setUserTypeNamesForTopLink(Collection userTypeNames) {
this.userTypeNames = userTypeNames;
}
/**
* convert to platform-independent representation
*/
private List getClasspathEntriesForTopLink() {
List result = new ArrayList(this.classpathEntries.size());
for (Iterator stream = this.classpathEntries.iterator(); stream.hasNext(); ) {
result.add(((String) stream.next()).replace('\\', '/'));
}
return result;
}
/**
* convert to platform-specific representation
*/
private void setClasspathEntriesForTopLink(List entries) {
this.classpathEntries = this.convertToClasspathEntries(entries);
}
/**
* convert to platform-specific representation
*/
private List convertToClasspathEntries(List entries) {
List result = new Vector(entries.size());
for (Iterator stream = entries.iterator(); stream.hasNext(); ) {
result.add(new File((String) stream.next()).getPath());
}
return result;
}
/**
* reset the "stub" interfaces because they are faulted in during
* #postProjectBuild() as "normal" classes;
*/
public void postProjectBuild() {
super.postProjectBuild();
this.configureImpliedStubInterfaces();
}
private void configureImpliedStubInterfaces() {
// no need to synchronize 'types' during a read
for (Iterator stream = this.types.values().iterator(); stream.hasNext(); ) {
((MWClass) stream.next()).configureImpliedStubInterfaces();
}
}
/**
* Return whether the specified type is a "user" type;
* i.e. it is neither a stub nor a "core" type.
*/
private boolean typeIsUserSupplied(MWClass type) {
if (type.isStub()) {
return false;
}
if (coreClassNamesContains(type.getName())) {
return false;
}
return true;
}
// ********** static methods **********
/**
* return the names of the "core" classes,
* i.e. those that are never written out to XML files
*/
public static Iterator coreClassNames() {
return getCoreClassNames().iterator();
}
/**
* return whether the specified name is among the
* "core" classes, i.e. those classes that are never
* written out to XML files
*/
public static boolean coreClassNamesContains(String typeName) {
return getCoreClassNames().contains(typeName);
}
private static synchronized Set getCoreClassNames() {
if (coreClassNames == null) {
coreClassNames = buildCoreClassNames();
}
return coreClassNames;
}
private static Set buildCoreClassNames() {
Set result = new HashSet(10000);
// void, boolean, int, float, etc.
CollectionTools.addAll(result, MWClass.nonReferenceClassNames());
List locations = buildCoreClassLocations();
Classpath cp = new Classpath(locations);
cp.addClassNamesTo(result);
return result;
}
private static List<String> buildCoreClassLocations() {
List<String> locations = new ArrayList<String>();
for (int i = 0; i < CORE_KEYS.length; i++) {
String classpath = Classpath.locationFor(CORE_KEYS[i]);
if (!locations.contains(classpath)) {
locations.add(classpath);
}
}
return locations;
}
/**
* return the specified name is among the
* "core" classes, i.e. those classes that are never
* written out to XML files
*/
public static String coreClassNameIgnoreCase(String typeName) {
return (String) getCoreClassNamesLowerCase().get(typeName.toLowerCase());
}
private static synchronized Map getCoreClassNamesLowerCase() {
if (coreClassNamesLowerCase == null) {
coreClassNamesLowerCase = buildCoreClassNamesLowerCase();
}
return coreClassNamesLowerCase;
}
private static Map buildCoreClassNamesLowerCase() {
Set names = getCoreClassNames();
Map result = new HashMap(names.size());
for (Iterator stream = names.iterator(); stream.hasNext(); ) {
String name = (String) stream.next();
result.put(name.toLowerCase(), name);
}
return result;
}
public boolean isPersistLastRefresh() {
return persistLastRefresh;
}
public void setPersistLastRefresh(boolean persistLastRefresh) {
//must assure all classes get re-written next save if value changes from true to false
if (!persistLastRefresh) {
Iterator userTypeIter = this.userTypes();
while(userTypeIter.hasNext()) {
((MWClass)userTypeIter.next()).markBranchDirty();
}
}
this.persistLastRefresh = persistLastRefresh;
}
}