blob: 8ed83a74b9201e8611681b4357b580fa9bfdf7cf [file] [log] [blame]
package org.eclipse.jem.internal.beaninfo.adapters;
/*******************************************************************************
* Copyright (c) 2001, 2003 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
/*
* $RCSfile: BeaninfoNature.java,v $
* $Revision: 1.1.4.1 $ $Date: 2003/12/16 19:28:47 $
*/
import java.io.*;
import java.text.MessageFormat;
import java.util.*;
import org.apache.xerces.jaxp.DocumentBuilderFactoryImpl;
import org.apache.xml.serialize.*;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.jem.internal.proxy.core.*;
import org.eclipse.jdt.core.*;
import org.eclipse.jdt.launching.JavaRuntime;
import org.eclipse.jdt.launching.VMRunnerConfiguration;
import org.w3c.dom.*;
import org.xml.sax.InputSource;
import com.ibm.etools.emf.workbench.ResourceHandler;
import org.eclipse.jem.internal.java.adapters.JavaXMIFactoryImpl;
import org.eclipse.jem.internal.java.beaninfo.IIntrospectionAdapter;
import org.eclipse.jem.internal.java.init.JavaInit;
import org.eclipse.jem.internal.plugin.*;
/**
* The beaninfo nature. It is created for a project and holds the
* necessary info for beaninfo to be performed on a project.
*/
public class BeaninfoNature implements IProjectNature {
public static final String NATURE_ID = "org.eclipse.jem.beaninfo.BeanInfoNature"; //$NON-NLS-1$
public static final String P_BEANINFO_SEARCH_PATH = ".beaninfoConfig"; //$NON-NLS-1$
// Persistent key
private ResourceTracker resourceTracker;
// This class listens for changes to the beaninfo paths file, and if changed it marks all stale
// so the next time anything is needed it will recycle the vm. It will also listen about to close or
// about to delete of the project so that it can cleanup.
private class ResourceTracker implements IResourceChangeListener{
public void resourceChanged(IResourceChangeEvent e) {
// About to close or delete the project and it is ours, so we need to cleanup.
if (e.getType() == IResourceChangeEvent.PRE_CLOSE || e.getType() == IResourceChangeEvent.PRE_DELETE) {
// Performance: It has been noted that dres.equals(...) can be slow with the number
// of visits done. Checking just the last segment (getName()) first before checking
// the entire resource provides faster testing. If the last segment is not equal,
// then the entire resource could not be equal.
IResource eventResource = e.getResource();
if (eventResource.getName().equals(getProject().getName()) && eventResource.equals(getProject())) {
cleanup(false);
return;
}
}
// Note: the BeaninfoModelSynchronizer takes care of both .classpath and .beaninfoconfig changes
// in this project and any required projects.
}
}
private ProxyFactoryRegistry.IRegistryListener registryListener = new ProxyFactoryRegistry.IRegistryListener() {
/**
* @see org.eclipse.jem.internal.proxy.core.ProxyFactoryRegistry.IRegistryListener#registryTerminated(ProxyFactoryRegistry)
*/
public void registryTerminated(ProxyFactoryRegistry registry) {
markAllStale();
};
};
/**
* Get the runtime nature for the project, create it if necessary.
*/
public static BeaninfoNature getRuntime(IProject project) throws CoreException {
if (project.hasNature(NATURE_ID))
return (BeaninfoNature) project.getNature(NATURE_ID);
else
return createRuntime(project);
}
/**
* Test if this is a valid project for a Beaninfo Nature. It must be
* a JavaProject.
*/
public static boolean isValidProject(IProject project) {
try {
return project.hasNature(JavaCore.NATURE_ID);
} catch (CoreException e) {
return false;
}
}
/**
* Create the runtime.
*/
private static BeaninfoNature createRuntime(IProject project) throws CoreException {
if (!isValidProject(project))
throw new CoreException(
new Status(
IStatus.ERROR,
BeaninfoPlugin.PI_BEANINFO,
0,
MessageFormat.format(
BeaninfoPlugin.getPlugin().getDescriptor().getResourceString(BeaninfoProperties.INTROSPECTFAILED),
new Object[] { project.getName()}),
null));
addNatureToProject(project, NATURE_ID);
return (BeaninfoNature) project.getNature(NATURE_ID);
}
private static void addNatureToProject(IProject proj, String natureId) throws CoreException {
IProjectDescription description = proj.getDescription();
String[] prevNatures = description.getNatureIds();
String[] newNatures = new String[prevNatures.length + 1];
System.arraycopy(prevNatures, 0, newNatures, 0, prevNatures.length);
newNatures[prevNatures.length] = natureId;
description.setNatureIds(newNatures);
proj.setDescription(description, null);
}
private IProject fProject;
protected ProxyFactoryRegistry fRegistry;
protected ResourceSet javaRSet;
protected BeaninfoModelSynchronizer fSynchronizer;
protected static BeaninfoJavaReflectionKeyExtension fReflectionKeyExtension;
/**
* Configures the project with this nature.
* This is called by <code>IProject.getNature</code> and should not
* be called directly by clients.
* The nature extension id is added to the list of natures on the project by
* <code>IProject.getNature</code>, and need not be added here.
*
* @exception CoreException if this method fails.
*/
public void configure() throws CoreException {
}
/**
* Removes this nature from the project, performing any required deconfiguration.
* This is called by <code>IProject.removeNature</code> and should not
* be called directly by clients.
* The nature id is removed from the list of natures on the project by
* <code>IProject.removeNature</code>, and need not be removed here.
*
* @exception CoreException if this method fails.
*/
public void deconfigure() throws CoreException {
removeSharedProperty(P_BEANINFO_SEARCH_PATH, null);
cleanup(true);
}
/**
* Return a new ResourceSet that is linked correctly to this Beaninfo Nature.
* This links up a ResourceSet so that it will work correctly with this nature.
* It makes sure that going through the ResourceSet that any "java:/..."
* classes can be found and it makes sure that any new classes are placed into the
* nature's resource set and not resource set doing the calling.
*
* The resourceset will have a context assigned to it.
*
* This should be used any time a resource set is needed that is not the
* project wide resource set associated with beaninfos, but will reference
* Java Model classes or instantiate.
*/
public ResourceSet newResourceSet() {
SpecialResourceSet rset = new SpecialResourceSet();
rset.add(new ResourceHandler() {
public EObject getEObjectFailed(ResourceSet originatingResourceSet, URI uri, boolean loadOnDemand) {
return null; // We don't have any special things we can do in this case.
}
public Resource getResource(ResourceSet originatingResourceSet, URI uri) {
// Always try to get it out of the nature's resource set because it may of been loaded there either as
// the "java:..." type or it could of been an override extra file (such as an override EMF package, for
// example jcf has a side package containing the definition of the new attribute type. That file
// will also be loaded into this resourceset. So to find it we need to go in here and try.
//
// However, if not found we won't go and try to load the resource. That could load in the wrong place.
return getResourceSet().getResource(uri, false);
}
public Resource createResource(ResourceSet originatingResourceSet, URI uri) {
// This is the one. It has got here because it couldn't find a resource already loaded.
// If it is a "java:/..." protocol resource, then we want to make sure it is loaded at the BeaninfoNature context
// instead of the lower one.
if (JavaXMIFactoryImpl.SCHEME.equals(uri.scheme()))
return getResourceSet().getResource(uri, true);
else
return null;
}
});
return rset;
}
/**
* Clean up, this means either the project is being closed, deleted, or it means that
* the nature is being removed from the project. Either way that means to
* terminate the VM and remove what we added to the context if the flag says clear it.
*/
protected void cleanup(boolean clearResults) {
getProject().getWorkspace().removeResourceChangeListener(resourceTracker);
resourceTracker = null;
fSynchronizer.stopSynchronizer(clearResults);
Init.cleanup(javaRSet, clearResults);
if (fRegistry != null)
fRegistry.terminateRegistry();
javaRSet = null;
fRegistry = null;
fProject = null;
fSynchronizer = null;
}
/**
* Returns the project to which this project nature applies.
*
* @return the project handle
*/
public IProject getProject() {
return fProject;
}
/**
* Sets the project to which this nature applies.
* Used when instantiating this project nature runtime.
* This is called by <code>IProject.addNature</code>
* and should not be called directly by clients.
*
* @param project the project to which this nature applies
*/
public void setProject(IProject project) {
fProject = project;
try {
// The nature has been started for this project, need to setup the introspection process now.
JavaEMFNature javaNature = JavaEMFNature.createRuntime(fProject);
JavaInit.init();
if (fReflectionKeyExtension == null) {
// Register the reflection key extension.
fReflectionKeyExtension = new BeaninfoJavaReflectionKeyExtension();
JavaXMIFactoryImpl.INSTANCE.registerReflectionKeyExtension(fReflectionKeyExtension);
}
javaRSet = javaNature.getResourceSet();
Init.initialize(javaRSet, new IBeaninfoSupplier() {
public ProxyFactoryRegistry getRegistry() {
return BeaninfoNature.this.getRegistry();
}
public boolean isRegistryCreated() {
return BeaninfoNature.this.isRegistryCreated();
}
public void closeRegistry() {
BeaninfoNature.this.closeRegistry();
}
});
fSynchronizer =
new BeaninfoModelSynchronizer(
(BeaninfoAdapterFactory) EcoreUtil.getAdapterFactory(javaRSet.getAdapterFactories(), IIntrospectionAdapter.ADAPTER_KEY),
JavaCore.create(javaNature.getProject()));
resourceTracker = new ResourceTracker();
project.getWorkspace().addResourceChangeListener(resourceTracker);
} catch (CoreException e) {
BeaninfoPlugin.getPlugin().getMsgLogger().log(e.getStatus());
}
}
/**
* Get the registry, creating it if necessary.
*/
public ProxyFactoryRegistry getRegistry() {
return getRegistry(new NullProgressMonitor());
}
/**
* Close the registry. It needs to be recycled because a class has changed
* and now the new class needs to be accessed.
*/
protected void closeRegistry() {
ProxyFactoryRegistry reg = null;
synchronized (this) {
reg = fRegistry;
fRegistry = null;
}
if (reg != null) {
reg.removeRegistryListener(registryListener);
reg.terminateRegistry();
}
}
public synchronized ProxyFactoryRegistry getRegistry(IProgressMonitor pm) {
if (fRegistry == null) {
try {
fRegistry = ProxyPlugin.getPlugin().startImplementation(fProject, "Beaninfo", //$NON-NLS-1$
new IConfigurationContributor[] { getConfigurationContributor()}, pm);
fRegistry.addRegistryListener(registryListener);
} catch (CoreException e) {
BeaninfoPlugin.getPlugin().getMsgLogger().log(e.getStatus());
}
}
return fRegistry;
}
public synchronized boolean isRegistryCreated() {
return fRegistry != null;
}
/**
* Check to see if the nature is still valid. If the project has been
* renamed, the nature is still around, but the project has been closed.
* So the nature is now invalid.
*
* @return Is this a valid nature. I.e. is the project still open.
*/
public boolean isValidNature() {
return fProject != null;
}
/**
* Set the search path onto the registry.
*/
protected void setProxySearchPath(ProxyFactoryRegistry registry, List searchPaths) {
if (searchPaths != null) {
String[] stringSearchPath = (String[]) searchPaths.toArray(new String[searchPaths.size()]);
Utilities.setBeanInfoSearchPath(registry, stringSearchPath);
} else
Utilities.setBeanInfoSearchPath(registry, null);
}
/*
* Get the search path in the old format.
*/
private BeaninfoSearchPathEntry[] getOldFormatSearchPath(Element root) {
NodeList children = root.getChildNodes();
int childrenLength = children.getLength();
ArrayList childrenList = new ArrayList(childrenLength);
for (int i = 0; i < childrenLength; i++) {
Node child = children.item(i);
BeaninfoSearchPathEntry bentry = BeaninfoSearchPathEntry.readEntry(child);
if (bentry != null)
childrenList.add(bentry);
}
return (BeaninfoSearchPathEntry[]) childrenList.toArray(new BeaninfoSearchPathEntry[childrenList.size()]);
}
/*
* Convert the old format to new format.
*/
private BeaninfosDoc convertOldFormatSearchPath(Element root) {
BeaninfoSearchPathEntry[] entries = getOldFormatSearchPath(root);
try {
IJavaProject jp = JavaCore.create(getProject());
IClasspathEntry[] cpEntries = jp.getRawClasspath();
HashMap resolvedEntries = new HashMap(cpEntries.length);
for (int i = 0; i < cpEntries.length; i++) {
IClasspathEntry resolved = JavaCore.getResolvedClasspathEntry(cpEntries[i]);
if (resolved != null)
resolvedEntries.put(resolved.getPath(), new Integer(i));
}
List newentries = new ArrayList(entries.length);
for (int i = 0; i < entries.length; i++) {
IPath pkgPath = new Path(entries[i].getPackageName().replace('.', '/'));
try {
IPackageFragment frag = (IPackageFragment) jp.findElement(pkgPath); // Find the first match
if (frag != null) {
IPackageFragmentRoot froot = (IPackageFragmentRoot) frag.getParent();
Integer index = (Integer) resolvedEntries.get(froot.getPath());
if (index != null) {
IClasspathEntry cpe = cpEntries[index.intValue()];
newentries.add(new SearchpathEntry(cpe.getEntryKind(), cpe.getPath(), frag.getElementName()));
}
}
} catch (ClassCastException e) {
// It didn't find a IPackageFragment, it should of, so skip this entry.
}
}
return new BeaninfosDoc((IBeaninfosDocEntry[]) newentries.toArray(new IBeaninfosDocEntry[newentries.size()]));
} catch (JavaModelException e) {
}
return null;
}
private static final String ENCODING = "UTF-8"; //$NON-NLS-1$
private static final String sSearchPathElementName = "searchPath"; //$NON-NLS-1$
// Old format root element name (WSAD 4.0.0)
static final String sBeaninfos = "beaninfos"; // Root element name //$NON-NLS-1$
/**
* Get the persistent search path. The object returned is a copy of the
* list, and it can be modified, but it won't be reflected back into the
* nature.
*/
public BeaninfosDoc getSearchPath() {
BeaninfosDoc bdoc = null;
try {
InputStream property = getSharedProperty(P_BEANINFO_SEARCH_PATH);
if (property != null) {
try {
// Need to reconstruct from the XML format.
DocumentBuilderFactoryImpl bldrFactory = new DocumentBuilderFactoryImpl();
Document doc = bldrFactory.newDocumentBuilder().parse(new InputSource(new InputStreamReader(property, ENCODING)));
Element root = doc.getDocumentElement();
if (root != null && root.getNodeName().equalsIgnoreCase(sSearchPathElementName)) {
// Old format. Need to convert to new format.
bdoc = convertOldFormatSearchPath(root);
setSearchPath(bdoc); // Now put out the converted format.
} else if (root != null && root.getNodeName().equalsIgnoreCase(sBeaninfos)) {
// New format
bdoc = BeaninfosDoc.readEntry(new DOMReader(), root, getProject());
}
} finally {
try {
property.close();
} catch (IOException e) {
}
}
}
} catch (CoreException e) {
BeaninfoPlugin.getPlugin().getMsgLogger().log(e.getStatus());
} catch (Exception e) {
BeaninfoPlugin.getPlugin().getMsgLogger().log(new Status(IStatus.WARNING, BeaninfoPlugin.PI_BEANINFO, 0, "", e));
}
return bdoc;
}
/**
* Set the persistent search path. No progress monitor.
*/
public void setSearchPath(BeaninfosDoc searchPath) throws CoreException {
setSearchPath(searchPath, null);
}
/**
* Set the persistent search path with a progress monitor
*/
public void setSearchPath(BeaninfosDoc searchPath, IProgressMonitor monitor) throws CoreException {
String property = null;
if (searchPath != null && searchPath.getSearchpath().length > 0) {
try {
DocumentBuilderFactoryImpl bldrFactory = new DocumentBuilderFactoryImpl();
Document doc = bldrFactory.newDocumentBuilder().newDocument();
Element root = doc.createElement(sBeaninfos); // Create Root Element
IBeaninfosDocEntry[] entries = searchPath.getSearchpath();
for (int i = 0; i < entries.length; i++)
root.appendChild(entries[i].writeEntry(doc, getProject())); // Add to the search path
doc.appendChild(root); // Add Root to Document
OutputFormat format = new OutputFormat(doc); //Serialize DOM
format.setIndenting(true);
StringWriter strWriter = new StringWriter();
Serializer serial = SerializerFactory.getSerializerFactory(format.getMethod()).makeSerializer(strWriter, format);
serial.asDOMSerializer().serialize(doc.getDocumentElement());
property = strWriter.toString();
} catch (Exception e) {
}
}
if (property != null) {
// If it hasn't changed, don't write it back out. This is so that if the file hasn't
// been checked out and it is the same, we don't want to bother the user. This is because
// we don't know if the user had simply browsed the search path or had actually changed and
// set it back to what it was. In either of those cases it would be a bother to ask the
// user to checkout the file.
InputStream is = getSharedProperty(P_BEANINFO_SEARCH_PATH);
if (is != null) {
try {
try {
InputStreamReader reader = new InputStreamReader(is, ENCODING);
char[] chars = new char[1000];
StringBuffer oldProperty = new StringBuffer(1000);
int read = reader.read(chars);
while (read != -1) {
oldProperty.append(chars, 0, read);
read = reader.read(chars);
}
if (oldProperty.toString().equals(property))
return;
} catch (IOException e) {
} // Didn't change.
} finally {
try {
is.close();
} catch (IOException e) {
}
}
}
setSharedProperty(P_BEANINFO_SEARCH_PATH, property, monitor);
} else
removeSharedProperty(P_BEANINFO_SEARCH_PATH, monitor);
}
/**
* Return the resource set for all java packages in this nature.
*/
public ResourceSet getResourceSet() {
return javaRSet;
}
protected void markAllStale() {
// Mark all stale so that the registry will be recycled.
if (fRegistry != null) {
// We have a registry running, we need to indicate recycle is needed.
fSynchronizer.getAdapterFactory().markAllStale();
// Mark all stale. Next time we need anything it will be recycled.
}
}
/**
* Compute the file name to use for a given shared property
*/
protected String computeSharedPropertyFileName(QualifiedName qName) {
return qName.getLocalName();
}
/**
* Retrieve a shared property on a project. If the property is not defined, answers null.
* Note that it is orthogonal to IResource persistent properties, and client code has to decide
* which form of storage to use appropriately. Shared properties produce real resource files which
* can be shared through a VCM onto a server. Persistent properties are not shareable.
*
*/
protected InputStream getSharedProperty(String propertyFileName) throws CoreException {
IFile rscFile = getProject().getFile(propertyFileName);
if (rscFile.exists())
return rscFile.getContents(true);
else
return null;
}
/**
* Record a shared persistent property onto a project.
* Note that it is orthogonal to IResource persistent properties, and client code has to decide
* which form of storage to use appropriately. Shared properties produce real resource files which
* can be shared through a VCM onto a server. Persistent properties are not shareable.
*
* shared properties end up in resource files, and thus cannot be modified during
* delta notifications (a CoreException would then be thrown).
*
*/
protected void setSharedProperty(String propertyName, String value, IProgressMonitor monitor) throws CoreException {
try {
IFile rscFile = getProject().getFile(propertyName);
InputStream input = new ByteArrayInputStream(value.getBytes(ENCODING));
// update the resource content
if (rscFile.exists()) {
rscFile.setContents(input, true, false, null);
} else {
rscFile.create(input, true, monitor);
}
} catch (UnsupportedEncodingException e) {
}
}
/**
* Remove a shared persistent property onto a project.
* Note that it is orthogonal to IResource persistent properties, and client code has to decide
* which form of storage to use appropriately. Shared properties produce real resource files which
* can be shared through a VCM onto a server. Persistent properties are not shareable.
*
* shared properties end up in resource files, and thus cannot be modified during
* delta notifications (a CoreException would then be thrown).
*
*/
protected void removeSharedProperty(String propertyName, IProgressMonitor monitor) throws CoreException {
IFile rscFile = getProject().getFile(propertyName);
rscFile.delete(true, true, monitor);
}
/**
* Return a configuration contributor that sets up a vm to allow
* introspection. This will make sure the appropriate paths
* are in the classpath to allow access to the beaninfos, and
* it will setup the beaninfo search path for this project.
*/
public IConfigurationContributor getConfigurationContributor() {
return new ConfigurationContributor(getSearchPath());
}
private static final IPath JRE_LIB_VARIABLE_PATH = new Path(JavaRuntime.JRELIB_VARIABLE); // TODO Remove when we handle containers
private class ConfigurationContributor implements IConfigurationContributor {
BeaninfosDoc doc;
List computedSearchPath = new ArrayList();
// Compute the search path as we compute the classpaths. This is saved because it will be used in a separate call.
HashSet visitedVariablepaths; // Visited registered variable paths, so we don't visit them again.
List variableContributors = new ArrayList(0); // Variable contributors that were found.
public ConfigurationContributor(BeaninfosDoc doc) {
this.doc = doc;
}
/**
* Method to update any class paths with any
* paths that need to be added to a VM. In this case, it is
* the proxyvm.jar that needs to be added. This jar contains
* the common code that is required by any VM for proxy
* support.
*/
public void contributeClasspaths(List classPaths, IClasspathContributionController controller) throws CoreException {
// Need to find any additional beaninfo jars. They can be pointed to within this projects path,
// or they can be found in pre-req'd project (assuming they are exported).
HashSet visitedProjects = new HashSet();
visitedVariablepaths = new HashSet();
try {
contributeClasspathsForProject(classPaths, controller, getProject(), visitedProjects, doc);
// Add the beaninfovm.jar and any nls to the end of the classpath.
controller.contributeClasspath(ProxyPlugin.getPlugin().urlLocalizeFromPluginDescriptorAndFragments(BeaninfoPlugin.getPlugin().getDescriptor(), "vm/beaninfovm.jar"), //$NON-NLS-1$
classPaths, -1);
} finally {
visitedVariablepaths = null;
}
// Now turn the var elements into contributors.
for (ListIterator itr = variableContributors.listIterator(); itr.hasNext();) {
IConfigurationElement v = (IConfigurationElement) itr.next();
IConfigurationContributor contrib = null;
try {
contrib = (IConfigurationContributor) v.createExecutableExtension(BeaninfoPlugin.PI_CONTRIBUTOR);
} catch (ClassCastException e) {
BeaninfoPlugin.getPlugin().getMsgLogger().log(new Status(IStatus.WARNING, BeaninfoPlugin.PI_BEANINFO, 0, "", e)); //$NON-NLS-1$
}
itr.set(contrib); // Set to what should be used, null is valid for not found.
if (contrib != null)
contrib.contributeClasspaths(classPaths, controller);
}
}
private IClasspathEntry get(IClasspathEntry[] array, InternalCPEntry cpe) {
for (int i = 0; i < array.length; i++) {
if (cpe.equals(array[i]))
return array[i];
}
return null;
}
/*
* Contribute classpaths for the specified project. If doc is passed in, then this is the top level and
* all should be added. If no doc, then this is pre-req'd project, and then we will handle exported entries only.
*/
protected void contributeClasspathsForProject(
List classPaths,
IClasspathContributionController controller,
IProject project,
HashSet visitedProjects,
BeaninfosDoc doc)
throws CoreException {
if (visitedProjects.contains(project))
return;
visitedProjects.add(project);
IJavaProject jProject = JavaCore.create(project);
IClasspathEntry[] rawPath = jProject.getRawClasspath();
// List of classpath entries for this project that have already been processed in the search path list.
// This is so that at the end when we process the classpath to add in any implicit beaninfos/search paths,
// we know these had been explicitly handled already.
List contributedICPEs = new ArrayList();
InternalCPEntry working = new InternalCPEntry(); // A working copy that we keep reusing.
// Search path of this project
IBeaninfosDocEntry[] entries = null;
if (doc != null)
entries = doc.getSearchpath();
else {
BeaninfosDoc adoc = BeaninfoNature.getRuntime(project).getSearchPath();
entries = adoc != null ? adoc.getSearchpath() : new IBeaninfosDocEntry[0];
}
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
for (int i = 0; i < entries.length; i++) {
IBeaninfosDocEntry entry = entries[i];
if (entry instanceof BeaninfoEntry) {
BeaninfoEntry be = (BeaninfoEntry) entry;
if (doc != null || be.isExported()) {
// First project or this is an exported beaninfo
Object cp = be.getClasspath();
if (cp instanceof IProject)
controller.contributeProject((IProject) cp, classPaths, -1);
else if (cp instanceof String)
controller.contributeClasspath((String) cp, classPaths, -1);
else if (cp instanceof String[])
controller.contributeClasspath((String[]) cp, classPaths, -1);
else
continue; // It was an invalid entry, don't add in its searchpaths.
// Now add in the package names.
SearchpathEntry[] sees = be.getSearchPaths();
for (int j = 0; j < sees.length; j++) {
SearchpathEntry searchpathEntry = sees[j];
if (!computedSearchPath.contains(searchpathEntry.getPackage()))
computedSearchPath.add(searchpathEntry.getPackage());
}
}
} else {
// Just a search path entry. There is no beaninfo jar to pick up.
SearchpathEntry se = (SearchpathEntry) entry;
working.setEntry(se.getKind(), se.getPath());
int cndx = contributedICPEs.indexOf(working);
if (cndx == -1) {
InternalCPEntry icpe = new InternalCPEntry(working.getKind(), working.getPath());
contributedICPEs.add(icpe); // Keep a record that this entry has been used.
if (doc == null) {
// This is the first time we've found this entry and we are in a nested project, find the raw classpath entry to see
// if this entry is exported. Only do it if exported. (Note: exported is only used on non-source. Source are always exported).
IClasspathEntry cpe = get(rawPath, icpe);
if (cpe == null || (cpe.getEntryKind() != IClasspathEntry.CPE_SOURCE && !cpe.isExported())) {
icpe.setIsExported(false); // Mark it as exported so if found again it won't be used.
continue; // Not exist or not exported, so we don't want it here either.
}
}
} else {
InternalCPEntry icpe = (InternalCPEntry) contributedICPEs.get(cndx);
if (doc == null && !icpe.isExported())
continue; // We've already determined it is not exported, so don't use it.
}
String pkg = se.getPackage();
if (pkg != null) {
// Explicit search path
if (!computedSearchPath.contains(pkg))
computedSearchPath.add(pkg);
} else {
// Process if this is an implicit search path kind of entry.
// I.e. It is just kind/path and no packagename. This means
// find the implicit searchpaths for this entry and put them
// in order here now. This can only be used on implicit kind
// of classpath entries. Any others don't have any implicit search paths
// so they are processed, just ignored.
processImplicitSearchPath(classPaths, controller, visitedProjects, root, se.getKind(), se.getPath());
}
}
}
// Now we need to go through our raw classpath to handle any not already handled.
// We only handle implicit search path from the project or registered variable.
for (int i = 0; i < rawPath.length; i++) {
IClasspathEntry entry = rawPath[i];
working.setEntry(entry);
if (contributedICPEs.contains(working))
continue; // We've already handled it above.
processImplicitSearchPath(classPaths, controller, visitedProjects, root, entry.getEntryKind(), entry.getPath());
}
}
protected void processImplicitSearchPath(
List classPaths,
IClasspathContributionController controller,
HashSet visitedProjects,
IWorkspaceRoot root,
int kind,
IPath path)
throws CoreException {
// Use the implicit search path from the project or registered variable.
// For now, only projects.
if (kind == IClasspathEntry.CPE_PROJECT) {
IProject reqProject = (IProject) root.findMember(path.lastSegment());
// Project entries only have one segment.
if (reqProject != null && reqProject.isOpen())
contributeClasspathsForProject(classPaths, controller, reqProject, visitedProjects, null);
} else if (kind == IClasspathEntry.CPE_VARIABLE) {
// We only handle variables as being registered.
if (path == null || path.segmentCount() == 0)
return; // No path information to process.
// First we handle the generic kind of for just the variable itself (which is segment 0).
IPath varpath = path.segmentCount() == 1 ? path : path.removeLastSegments(path.segmentCount() - 1);
if (!visitedVariablepaths.contains(varpath)) {
visitedVariablepaths.add(varpath);
BeaninfoRegistration[] registrations = BeaninfoPlugin.getPlugin().getRegistrations(varpath);
if (registrations != null)
processBeaninfoRegistrations(registrations, classPaths, controller);
}
// Now process for the specific path (which would be variable followed by some subpaths).
if (path.segmentCount() > 1 && !visitedVariablepaths.contains(path)) {
visitedVariablepaths.add(path);
BeaninfoRegistration[] registrations = BeaninfoPlugin.getPlugin().getRegistrations(path);
if (registrations != null)
processBeaninfoRegistrations(registrations, classPaths, controller);
}
} else if (kind == IClasspathEntry.CPE_CONTAINER) {
// KLUDGE TODO For now we can't really handle containers, we will simply hard-code and only handle JRE container to JRE_LIB stuff.
if (path == null || path.segmentCount() == 0)
return; // No path information to process.
if (path.segment(0).equals(JavaRuntime.JRE_CONTAINER)) {
if (!visitedVariablepaths.contains(JRE_LIB_VARIABLE_PATH)) {
visitedVariablepaths.add(JRE_LIB_VARIABLE_PATH);
BeaninfoRegistration[] registrations = BeaninfoPlugin.getPlugin().getRegistrations(JRE_LIB_VARIABLE_PATH);
if (registrations != null)
processBeaninfoRegistrations(registrations, classPaths, controller);
}
}
}
}
protected void processBeaninfoRegistrations(
BeaninfoRegistration[] registrations,
List classPaths,
IClasspathContributionController controller)
throws CoreException {
for (int i = 0; i < registrations.length; i++)
processBeaninfoRegistration(registrations[i], classPaths, controller);
}
protected void processBeaninfoRegistration(
BeaninfoRegistration registration,
List classPaths,
IClasspathContributionController controller)
throws CoreException {
BeaninfosDoc doc = registration.getDoc();
if (doc == null)
return;
IConfigurationElement varElement = registration.getVariableElement();
if (varElement != null)
variableContributors.add(varElement);
IBeaninfosDocEntry[] entries = doc.getSearchpath();
for (int i = 0; i < entries.length; i++) {
IBeaninfosDocEntry entry = entries[i];
if (entry instanceof BeaninfoEntry) {
BeaninfoEntry be = (BeaninfoEntry) entry;
Object cp = be.getClasspath();
if (cp instanceof IProject)
controller.contributeProject((IProject) cp, classPaths, -1);
else if (cp instanceof String)
controller.contributeClasspath((String) cp, classPaths, -1);
else if (cp instanceof String[])
controller.contributeClasspath((String[]) cp, classPaths, -1);
else
continue; // It was an invalid entry, don't add in its searchpaths.
// Now add in the package names.
SearchpathEntry[] sees = be.getSearchPaths();
for (int j = 0; j < sees.length; j++) {
SearchpathEntry searchpathEntry = sees[j];
if (!computedSearchPath.contains(searchpathEntry.getPackage()))
computedSearchPath.add(searchpathEntry.getPackage());
}
} else {
// Just a search path entry. There is no beaninfo jar to pick up. The paths will be in the current classpath probably from the bean classes jar that this registration matches.
// There should be no paths or kinds. It should only be packagename.
String pkg = ((SearchpathEntry) entry).getPackage();
if (pkg != null) {
// Explicit search path
if (!computedSearchPath.contains(pkg))
computedSearchPath.add(pkg);
}
}
}
}
public void contributeToConfiguration(VMRunnerConfiguration config) {
for (int i = 0; i < variableContributors.size(); i++) {
IConfigurationContributor contrib = (IConfigurationContributor) variableContributors.get(i);
if (contrib != null)
contrib.contributeToConfiguration(config);
}
}
public void contributeToRegistry(ProxyFactoryRegistry registry) {
setProxySearchPath(registry, computedSearchPath);
for (int i = 0; i < variableContributors.size(); i++) {
IConfigurationContributor contrib = (IConfigurationContributor) variableContributors.get(i);
if (contrib != null)
contrib.contributeToRegistry(registry);
}
}
}
/*
* An internal type CPEntry because an actual
* entry also tests attachments, but we are only interested
* in kind/path.
*
* Note: This must not be used in a Hash... because its hashCode
* doesn't work for this. This is because there is no hashCode
* that we could compute that would allow IClasspathEntry's and
* InternalCPEntry's that are semantically equal to hash to the same value.
*/
private static class InternalCPEntry {
int kind;
IPath path;
boolean isExported = true;
public InternalCPEntry(int kind, IPath path) {
setEntry(kind, path);
}
public InternalCPEntry() {
}
/*
* Set if this entry is exported or not. This is not involved in
* the equality test.
*/
public boolean isExported() {
return isExported;
}
public void setIsExported(boolean isExported) {
this.isExported = isExported;
}
public int getKind() {
return kind;
}
public IPath getPath() {
return path;
}
public boolean equals(Object o) {
if (this == o)
return true;
if (o instanceof IClasspathEntry) {
IClasspathEntry ce = (IClasspathEntry) o;
return kind == ce.getEntryKind() && path.equals(ce.getPath());
}
if (o instanceof InternalCPEntry) {
InternalCPEntry ice = (InternalCPEntry) o;
return kind == ice.kind && path.equals(ice.path);
}
return false;
}
public void setEntry(IClasspathEntry entry) {
setEntry(entry.getEntryKind(), entry.getPath());
}
public void setEntry(int kind, IPath path) {
this.kind = kind;
this.path = path;
}
}
}