/******************************************************************************* * Copyright (c) 2001, 2006 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.jem.internal.beaninfo.adapters; /* */ import java.io.*; import java.text.MessageFormat; import java.util.*; import java.util.logging.Level; import javax.xml.parsers.*; import javax.xml.transform.*; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.eclipse.core.resources.*; import org.eclipse.core.runtime.*; import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; 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.emf.ecore.xmi.impl.XMIResourceFactoryImpl; import org.eclipse.jdt.core.*; import org.osgi.framework.Bundle; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xml.sax.InputSource; import org.eclipse.jem.internal.beaninfo.core.*; import org.eclipse.jem.internal.java.beaninfo.IIntrospectionAdapter; import org.eclipse.jem.internal.java.init.JavaInit; import org.eclipse.jem.internal.plugin.JavaEMFNature; import org.eclipse.jem.internal.proxy.core.*; import org.eclipse.jem.internal.proxy.core.IConfigurationContributionInfo.ContainerPaths; import org.eclipse.jem.java.adapters.JavaXMIFactory; import org.eclipse.jem.util.emf.workbench.ProjectResourceSet; import org.eclipse.jem.util.emf.workbench.ResourceHandler; /** * 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 = BeaninfoPlugin.PI_BEANINFO_PLUGINID + ".BeanInfoNature"; //$NON-NLS-1$ public static final String P_BEANINFO_SEARCH_PATH = ".beaninfoConfig"; //$NON-NLS-1$ public static final QualifiedName CONFIG_INFO_SESSION_KEY = new QualifiedName(BeaninfoPlugin.PI_BEANINFO_PLUGINID, "CONFIG_INFO"); //$NON-NLS-1$ public static final QualifiedName BEANINFO_CONTRIBUTORS_SESSION_KEY = new QualifiedName(BeaninfoPlugin.PI_BEANINFO_PLUGINID, "BEANINFO_CONTRIBUTORS"); //$NON-NLS-1$ private ProxyFactoryRegistry.IRegistryListener registryListener = new ProxyFactoryRegistry.IRegistryListener() { /** * @see org.eclipse.jem.internal.proxy.core.ProxyFactoryRegistry.IRegistryListener#registryTerminated(ProxyFactoryRegistry) */ public void registryTerminated(ProxyFactoryRegistry registry) { prematureRegistryTerminate(); markAllStale(); }; }; /** * Get the runtime nature for the project, create it if necessary. */ public static BeaninfoNature getRuntime(IProject project) throws CoreException { JavaEMFNature.createRuntime(project); // Must force JAVAEMFNature creation first before we try to get ours. There is a chicken/egg problem if we let our nature try to get JavaEMFNature during setProject. if (project.hasNature(NATURE_ID)) return (BeaninfoNature) project.getNature(NATURE_ID); else return createRuntime(project); } /** * Return whether this project has a BeanInfo runtime turned on. * * @param project * @return <code>true</code> if it has the a BeanInfo runtime. * @throws CoreException * * @since 1.0.0 */ public static boolean hasRuntime(IProject project) throws CoreException { return project.hasNature(NATURE_ID); } /** * 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_PLUGINID, 0, MessageFormat.format( BeanInfoAdapterMessages.INTROSPECT_FAILED_EXC_, new Object[] { project.getName(), BeanInfoAdapterMessages.BeaninfoNature_InvalidProject}), 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, true); } /** * Shutdown the nature. Called by BeanInfoPlugin to tell the nature that the plugin is being shutdown. * It needs to cleanup. * TODO <package-protected> because only BeanInfoPlugin should call it. (public for now but when we make * BeanInfoNature an API it will be moved into the same package as BeanInfoPlugin). * * @since 1.0.0 */ public void shutdown() { cleanup(true, false); } /** * Return a new ResourceSet that is linked correctly to this Beaninfo Nature. * <p> * 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. * <p> * 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. * <p> * An additional change is made too. The ResourceFactoryRegistry's extensionToResourceFactory map is modified * to have an "java"->XMIResourceFactory entry added to it if EMF Examples is loaded. EMF Examples add * the "java" extension and sets it to their own special JavaResourceFactory. * If EMF Examples is not loaded, then it falls back to the default "*" mapping, which is to XMIResourceFactory. * This normally causes problems for many * customers. If users of this resource set really want the EMF examples entry instead, after they retrieve the * new resource set they can do this: * <p> * <pre><code> * rset = beaninfoNature.newResourceSet(); * rset.getResourceFactoryRegistry().getExtensionToFactoryMap().remove("java"); * </code></pre> * * @return a ResourceSet that is specially connected to the JEM java model. * * @since 1.0.0 */ public ProjectResourceSet 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. // Kludge: Because of a bug (feature :-)) in XMLHandler.getPackageFromURI(), it doesn't use getResource(...,true) and it tries instead // to use uri inputstream to load the package when not found. This bypasses our special create resource and so // packages are not automatically created. So we need to do load on demand here instead if it is a java protocol. // EMF will not be fixing this. It is working as designed. return getResourceSet().getResource(uri, JavaXMIFactory.SCHEME.equals(uri.scheme())); } 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 (JavaXMIFactory.SCHEME.equals(uri.scheme())) return getResourceSet().getResource(uri, true); else return null; } }); // [71473] Restore "*.java" to be an XMIResource. If EMF Examples are loaded they overload this and load their special resource for "*.java" which we don't want. // If some user really wants that, they grab the resource resource set and remove our override. if (Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().containsKey("java")) { //$NON-NLS-1$ // Need to add an override to go to XMI instead. rset.getResourceFactoryRegistry().getExtensionToFactoryMap().put("java", new XMIResourceFactoryImpl()); //$NON-NLS-1$ } 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. * <p> * This should be called ONLY when this instance of the nature is no longer needed. It * will be recreated for any new uses. That is because we will be removing ourselves * from the list of active natures in the BeanInfoPlugin. * <p> * <b>Note:</b> This will be called from the BeanInfoCacheController. It knows when the project is * being closed or deleted. * * @param clearResults clear the results such that any JEM model objects have no BeanInfo * adapters attached to them. This allows BeanInfo to be GC'd without being hung onto. * * @param deregister Deregister from the BeanInfoPlugin. Normally this will always be true, but it * will be called with false when BeanInfoPlugin is calling back to shutdown. */ public synchronized void cleanup(boolean clearResults, boolean deregister) { if (deregister) BeaninfoPlugin.getPlugin().removeBeanInfoNature(this); fSynchronizer.stopSynchronizer(clearResults); Init.cleanup(javaRSet, clearResults); if (fRegistry != null) fRegistry.terminateRegistry(true); projectCleaned(); 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) { // BeanInfoCacheController.INSTANCE.getClass(); // Instantiates the controller if not already started. fProject = project; BeaninfoPlugin.getPlugin().addBeanInfoNature(this); 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(); JavaXMIFactory.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(); } public IProject getProject() { return BeaninfoNature.this.getProject(); } public ProjectResourceSet getNewResourceSet() { return BeaninfoNature.this.newResourceSet(); } public ResourceSet getProjectResourceSet() { return getResourceSet(); } }); fSynchronizer = new BeaninfoModelSynchronizer( (BeaninfoAdapterFactory) EcoreUtil.getAdapterFactory(javaRSet.getAdapterFactories(), IIntrospectionAdapter.ADAPTER_KEY), JavaCore.create(javaNature.getProject())); } catch (CoreException e) { BeaninfoPlugin.getPlugin().getLogger().log(e.getStatus()); } } /** * 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; try { // Wipe out the Session properties so that they are recomputed. getProject().setSessionProperty(CONFIG_INFO_SESSION_KEY, null); getProject().setSessionProperty(BEANINFO_CONTRIBUTORS_SESSION_KEY, null); } catch (CoreException e) { BeaninfoPlugin.getPlugin().getLogger().log(e, Level.INFO); } } if (reg != null) { reg.removeRegistryListener(registryListener); reg.terminateRegistry(); } } private static final String PI_CLASS = "class"; //$NON-NLS-1$ /** * Using the given configuration info, compute the BeanInfo config info needed. This sets the * session properties BEANINFO_CONTRIBUTORS_SESSION_KEY and CONFIG_INFO_SESSION_KEY. * * @param info * @throws CoreException * * @since 1.1.0 */ public static void computeBeanInfoConfigInfo(IConfigurationContributionInfo info) throws CoreException { // First time for this nature, or first time after registry reset. Need to compute the info. // It is possible for this to be called BEFORE the first usage of BeanInfo. The editor usually // brings up the editor's registry before it gets anything from BeanInfo. List contributorsList = new ArrayList(10); if (!info.getContainerIds().isEmpty()) { // Run through all of the visible container ids that are applicable and get BeanInfo contributors. Iterator containerIdItr = info.getContainerIds().values().iterator(); while (containerIdItr.hasNext()) { ContainerPaths containerPaths = (ContainerPaths) containerIdItr.next(); IConfigurationElement[] contributors = BeaninfoPlugin.getPlugin().getContainerIdContributors(containerPaths.getContainerId(), containerPaths.getVisibleContainerPaths()); for (int i = 0; i < contributors.length; i++) { try { Object contributor = contributors[i].createExecutableExtension(PI_CLASS); if (contributor instanceof IBeanInfoContributor) contributorsList.add(contributor); } catch (CoreException e) { BeaninfoPlugin.getPlugin().getLogger().log(e, Level.WARNING); } } } } if (!info.getPluginIds().isEmpty()) { // Run through all of the visible plugin ids that are applicable and get BeanInfo contributors. Iterator pluginIdItr = info.getPluginIds().entrySet().iterator(); while (pluginIdItr.hasNext()) { Map.Entry entry = (Map.Entry) pluginIdItr.next(); if (((Boolean) entry.getValue()).booleanValue()) { IConfigurationElement[] contributors = BeaninfoPlugin.getPlugin().getPluginContributors( (String) entry.getKey()); if (contributors != null) { for (int i = 0; i < contributors.length; i++) { try { Object contributor = contributors[i].createExecutableExtension(PI_CLASS); if (contributor instanceof IBeanInfoContributor) contributorsList.add(contributor); } catch (CoreException e) { BeaninfoPlugin.getPlugin().getLogger().log(e, Level.WARNING); } } } } } } // Save it for all beaninfo processing (and configuration processing if they implement proxy configuration contributor). IBeanInfoContributor[] explicitContributors = (IBeanInfoContributor[]) contributorsList.toArray(new IBeanInfoContributor[contributorsList.size()]); info.getJavaProject().getProject().setSessionProperty(BEANINFO_CONTRIBUTORS_SESSION_KEY, explicitContributors); // Save it for override processing. That happens over and over later after all config processing is done. // Do it last so that if there is a race condition, since this property is a flag to indicate we have data, // we need to make sure the Beaninfo data is already set at the point we set this. // We could actually set it twice because of this, but it is the same data, so, so what. info.getJavaProject().getProject().setSessionProperty(CONFIG_INFO_SESSION_KEY, info); } /** * Get registry, creating it if necessary. * @return the registry. * * @since 1.0.0 */ public ProxyFactoryRegistry getRegistry() { synchronized (this) { if (fRegistry != null) return fRegistry; } // Now need to start the appropriate job. In another class so that it can handle dynamically checking if // UI is available to even do this (it maybe not in a UI mode, so then just do it. CreateRegistryJobHandler.createRegistry(this); return fRegistry; } private static final long NO_PREMATURE_TERMINATE_TIME = -1; private int registryPrematureTerminateCount; private long registryLastPrematureTerminateTime = NO_PREMATURE_TERMINATE_TIME; /* * This is <package-protected> so that only the appropriate create job in this * package can call it. This is because this must be controlled to only be * done when build not in progress and serial access. */ void createRegistry(IProgressMonitor pm) { pm.beginTask(BeanInfoAdapterMessages.UICreateRegistryJobHandler_StartBeaninfoRegistry, 100); if (isRegistryCreated()) { pm.done(); return; // It had already been created. Could of been because threads were racing to do the creation, and one got there first. } // Test to see if we have terminated too many times within the last 10 minutes. If we have then don't try again. synchronized(this) { if (registryLastPrematureTerminateTime != NO_PREMATURE_TERMINATE_TIME) { long lastPrematureTerminateInterval = System.currentTimeMillis() - registryLastPrematureTerminateTime; // Don't try again within 1 sec of last premature terminate. It will still be bad. // Of if there have been more than 3 and it has been 10 mins since the last try. if (lastPrematureTerminateInterval < 3*1000 || (registryPrematureTerminateCount > 3 && lastPrematureTerminateInterval < 10 * 60 * 1000)) return; } } ProxyFactoryRegistry registry = null; try { ConfigurationContributor configurationContributor = (ConfigurationContributor) getConfigurationContributor(); configurationContributor.setNature(this); registry = ProxyLaunchSupport.startImplementation(fProject, "Beaninfo", //$NON-NLS-1$ new IConfigurationContributor[] { configurationContributor}, false, new SubProgressMonitor(pm, 100)); synchronized(this) { if (!isRegistryCreated()) { projectCleaned(); registry.addRegistryListener(registryListener); fRegistry = registry; } else { // It was created while we were creating. So use the current one. Terminate the one just created. Not needed. registry.terminateRegistry(false); } } } catch (CoreException e) { BeaninfoPlugin.getPlugin().getLogger().log(e.getStatus()); } finally { if (registry == null) { // It didn't create. Treat as premature terminate. prematureRegistryTerminate(); } pm.done(); } } /** * Called by others in package (BeaninfoCacheController) to let know a clean has occured. * * TODO this should be package-protected but until in same package as cache controller it is public. * @since 1.2.0 */ public synchronized void projectCleaned() { // On a clean we will reset the counters. registryPrematureTerminateCount = 0; registryLastPrematureTerminateTime = NO_PREMATURE_TERMINATE_TIME; } 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); } private static final String ENCODING = "UTF-8"; //$NON-NLS-1$ static final String sBeaninfos = "beaninfos"; // Root element name //$NON-NLS-1$ /** * Get the persistent search path. It is copy. */ 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. Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new InputSource(new InputStreamReader(property, ENCODING))); Element root = doc.getDocumentElement(); if (root != null && root.getNodeName().equalsIgnoreCase(sBeaninfos)) { bdoc = BeaninfosDoc.readEntry(new DOMReader(), root, getProject()); } } finally { try { property.close(); } catch (IOException e) { } } } } catch (CoreException e) { BeaninfoPlugin.getPlugin().getLogger().log(e.getStatus()); } catch (Exception e) { BeaninfoPlugin.getPlugin().getLogger().log(new Status(IStatus.WARNING, BeaninfoPlugin.PI_BEANINFO_PLUGINID, 0, "", e)); //$NON-NLS-1$ } 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 { Document doc = DocumentBuilderFactory.newInstance().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 StringWriter strWriter = new StringWriter(); Result result = new StreamResult(strWriter); Source source = new DOMSource(doc); Transformer transformer = TransformerFactory.newInstance().newTransformer(); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$ transformer.setOutputProperty(OutputKeys.METHOD, "xml"); //$NON-NLS-1$ transformer.transform(source, result); property = strWriter.toString(); } catch (TransformerConfigurationException e) { BeaninfoPlugin.getPlugin().getLogger().log(e, Level.WARNING); } catch (TransformerException e) { BeaninfoPlugin.getPlugin().getLogger().log(e, Level.WARNING); } catch (ParserConfigurationException e) { BeaninfoPlugin.getPlugin().getLogger().log(e, Level.WARNING); } catch (FactoryConfigurationError e) { BeaninfoPlugin.getPlugin().getLogger().log(e, Level.WARNING); } } 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 void prematureRegistryTerminate() { synchronized(BeaninfoNature.this) { registryPrematureTerminateCount++; registryLastPrematureTerminateTime = System.currentTimeMillis(); } } private static class ConfigurationContributor extends ConfigurationContributorAdapter { private BeaninfosDoc doc; List computedSearchPath; // The nature. If the nature is not set then this contributor is one // used by some other later proxy registry to get the beaninfo classes into their paths. In that case // we can expect the config info to be in the session variable for our use. Otherwise we will need to // add it here. Also don't set searchpath stuff if not nature because only the beaninfo one will do introspection. private BeaninfoNature nature; private IConfigurationContributionInfo info; private IBeanInfoContributor[] explicitContributors; public ConfigurationContributor(BeaninfosDoc doc) { this.doc = doc; } /* * Set that this is the nature contributor. Not null, means that this is the contributor being * used to setup the registry for the project's beaninfo nature. null (default) means that this * is one created to add to some editor's registry. * * Note: This MUST be set before initialize is called or it will not work correctly. If not set, it * will be considered not for BeanInfo nature directly. */ public void setNature(BeaninfoNature nature) { this.nature = nature; if (nature != null) computedSearchPath = new ArrayList(3); // We will be gathering this info. } /* (non-Javadoc) * @see org.eclipse.jem.internal.proxy.core.IConfigurationContributor#initialize(org.eclipse.jem.internal.proxy.core.IConfigurationContributionInfo) */ public void initialize(IConfigurationContributionInfo info) { this.info = info; try { if (info.getJavaProject().getProject().getSessionProperty(CONFIG_INFO_SESSION_KEY) == null) { // TODO For now we will rebuild for each time we open a registry, but it actually is only needed if a classpath // changes for some reason. At that point we can get it out of here. computeBeanInfoConfigInfo(info); } explicitContributors = (IBeanInfoContributor[]) info.getJavaProject().getProject().getSessionProperty(BEANINFO_CONTRIBUTORS_SESSION_KEY); } catch (CoreException e) { BeaninfoPlugin.getPlugin().getLogger().log(e); } } public void contributeClasspaths(final IConfigurationContributionController controller) throws CoreException { // Contribute for this project contributeClasspathsForProject(controller, info.getJavaProject().getProject(), doc, true); if (!info.getProjectPaths().isEmpty()) { // Run through all of the visible projects and contribute the classpaths (which come from the BeanInfo docs, if they have any). IWorkspaceRoot root = info.getJavaProject().getProject().getWorkspace().getRoot(); Iterator projIter = info.getProjectPaths().entrySet().iterator(); while (projIter.hasNext()) { Map.Entry entry = (Map.Entry) projIter.next(); if (((Boolean) entry.getValue()).booleanValue()) { IResource res = root.findMember((IPath) entry.getKey()); if (res instanceof IProject && ((IProject) res).isOpen() && BeaninfoNature.hasRuntime((IProject) res)) contributeClasspathsForProject(controller, (IProject) res, BeaninfoNature.getRuntime((IProject) res) .getSearchPath(), false); } } } if (!info.getContainerIds().isEmpty()) { // Run through all of the visible container ids that are applicable. Iterator containerIdItr = info.getContainerIds().values().iterator(); while (containerIdItr.hasNext()) { ContainerPaths containerPaths = (ContainerPaths) containerIdItr.next(); processBeaninfoEntries(BeaninfoPlugin.getPlugin().getContainerIdBeanInfos(containerPaths.getContainerId(), containerPaths.getVisibleContainerPaths()), controller, info.getJavaProject()); } } if (!info.getPluginIds().isEmpty()) { // Run through all of the visible plugin ids that are applicable. Iterator pluginIdItr = info.getPluginIds().entrySet().iterator(); while (pluginIdItr.hasNext()) { Map.Entry entry = (Map.Entry) pluginIdItr.next(); if (((Boolean) entry.getValue()).booleanValue()) { processBeaninfoEntries(BeaninfoPlugin.getPlugin().getPluginBeanInfos((String) entry.getKey()), controller, info.getJavaProject()); } } } if (!info.getContainers().isEmpty()) { // Run through all of the visible containers that implement IBeanInfoContributor and ask them for the contributions. Iterator containerItr = info.getContainers().entrySet().iterator(); while (containerItr.hasNext()) { Map.Entry entry = (Map.Entry) containerItr.next(); if (((Boolean) entry.getValue()).booleanValue()) { if (entry.getKey() instanceof IBeanInfoContributor) processBeaninfoEntries(((IBeanInfoContributor) entry.getKey()).getBeanInfoEntryContributions(info), controller, info.getJavaProject()); } } } // And finally run through the explicit contributors. for (int i = 0; i < explicitContributors.length; i++) { final IBeanInfoContributor contributor = explicitContributors[i]; processBeaninfoEntries(contributor.getBeanInfoEntryContributions(info), controller, info.getJavaProject()); if (contributor instanceof IConfigurationContributor) { SafeRunner.run(new ISafeRunnable() { public void handleException(Throwable exception) { // do nothing. by default platform logs. } public void run() throws Exception {; if (contributor instanceof IConfigurationContributor) ((IConfigurationContributor) contributor).contributeClasspaths(controller); } }); } } // Add the common to the end of the classpath. (Since we are now a jarred plugin, the common is not the plugin jar itself). // With bug 256441 we've reverted to putting only the "common.jar" on the classpath, though now it is its own bundle. See // https://bugs.eclipse.org/bugs/show_bug.cgi?id=256441 Bundle commonBundle = Platform.getBundle("org.eclipse.jem.beaninfo.vm.common"); controller.contributeClasspath(commonBundle, (IPath) null, IConfigurationContributionController.APPEND_USER_CLASSPATH, false); //$NON-NLS-1$ // Add the beaninfovm.jar and any nls to the end of the classpath. // https://bugs.eclipse.org/bugs/show_bug.cgi?id=256441 Bundle vmBundle = Platform.getBundle("org.eclipse.jem.beaninfo.vm"); controller.contributeClasspath(vmBundle, (IPath) null, IConfigurationContributionController.APPEND_USER_CLASSPATH, true); //$NON-NLS-1$ } private IClasspathEntry get(IClasspathEntry[] array, SearchpathEntry se) { for (int i = 0; i < array.length; i++) { if (array[i].getEntryKind() == se.getKind() && array[i].getPath().equals(se.getPath())) return array[i]; } return null; } private static final IBeaninfosDocEntry[] EMPTY_ENTRIES = new IBeaninfosDocEntry[0]; /* * 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( IConfigurationContributionController controller, IProject project, BeaninfosDoc doc, boolean toplevelProject) throws CoreException { IJavaProject jProject = JavaCore.create(project); IClasspathEntry[] rawPath = jProject.getRawClasspath(); // Search path of this project IBeaninfosDocEntry[] entries = (doc != null) ? doc.getSearchpath() : EMPTY_ENTRIES; for (int i = 0; i < entries.length; i++) { IBeaninfosDocEntry entry = entries[i]; if (entry instanceof BeaninfoEntry) { BeaninfoEntry be = (BeaninfoEntry) entry; if (toplevelProject || be.isExported()) { // First project or this is an exported beaninfo, so we process it. processBeaninfoEntry(be, controller, jProject); } } else if (nature != null){ // Just a search path entry. There is no beaninfo jar to pick up. // We have a nature, so we process search path. SearchpathEntry se = (SearchpathEntry) entry; if (!toplevelProject) { // 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, se); if (cpe == null || (cpe.getEntryKind() != IClasspathEntry.CPE_SOURCE && !cpe.isExported())) { continue; // Not exist or not exported, so we don't want it here either. } } String pkg = se.getPackage(); if (pkg != null) { // Explicit search path if (!computedSearchPath.contains(pkg)) computedSearchPath.add(pkg); } else { // We no longer allow this, but just to be on safe side we test for it. } } } } protected void processBeaninfoEntries( BeaninfoEntry[] entries, IConfigurationContributionController controller, IJavaProject javaProject) throws CoreException { if (entries != null) { for (int i = 0; i < entries.length; i++) processBeaninfoEntry(entries[i], controller, javaProject); } } protected void processBeaninfoEntry( BeaninfoEntry entry, IConfigurationContributionController controller, IJavaProject javaProject) throws CoreException { Object[] cps = entry.getClasspath(javaProject); for (int j = 0; j < cps.length; j++) { Object cp = cps[j]; if (cp instanceof IProject) controller.contributeProject((IProject) cp); else if (cp instanceof String) controller.contributeClasspath(ProxyLaunchSupport.convertStringPathToURL((String) cp), IConfigurationContributionController.APPEND_USER_CLASSPATH); else if (cp instanceof IPath) { IPath path = (IPath) cp; Bundle bundle = Platform.getBundle(path.segment(0)); if (bundle != null) controller.contributeClasspath(bundle, path.removeFirstSegments(1), IConfigurationContributionController.APPEND_USER_CLASSPATH, true); } } if (nature != null) { // Now add in the package names. SearchpathEntry[] sees = entry.getSearchPaths(); for (int j = 0; j < sees.length; j++) { SearchpathEntry searchpathEntry = sees[j]; if (!computedSearchPath.contains(searchpathEntry.getPackage())) computedSearchPath.add(searchpathEntry.getPackage()); } } } public void contributeToConfiguration(final ILaunchConfigurationWorkingCopy config) { for (int i = 0; i < explicitContributors.length; i++) { final int ii = i; SafeRunner.run(new ISafeRunnable() { public void handleException(Throwable exception) { // do nothing. by default platform logs. } public void run() throws Exception { IBeanInfoContributor contributor = explicitContributors[ii]; if (contributor instanceof IConfigurationContributor) ((IConfigurationContributor) contributor).contributeToConfiguration(config); } }); } } public void contributeToRegistry(final ProxyFactoryRegistry registry) { if (nature != null) nature.setProxySearchPath(registry, computedSearchPath); for (int i = 0; i < explicitContributors.length; i++) { final int ii = i; SafeRunner.run(new ISafeRunnable() { public void handleException(Throwable exception) { // do nothing. by default platform logs. } public void run() throws Exception { IBeanInfoContributor contributor = explicitContributors[ii]; if (contributor instanceof IConfigurationContributor) ((IConfigurationContributor) contributor).contributeToRegistry(registry); } }); } } } }