| /******************************************************************************* |
| * 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). |
| controller.contributeClasspath(BeaninfoPlugin.getPlugin().getBundle(), (IPath) null, IConfigurationContributionController.APPEND_USER_CLASSPATH, false); //$NON-NLS-1$ |
| |
| // Add the beaninfovm.jar and any nls to the end of the classpath. |
| controller.contributeClasspath(BeaninfoPlugin.getPlugin().getBundle(), "vm/beaninfovm.jar", 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); |
| } |
| }); |
| } |
| } |
| } |
| |
| |
| } |