| /****************************************************************************** |
| * Copyright (c) 2005 BEA Systems, Inc. |
| * 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: |
| * Konstantin Komissarchik - initial API and implementation |
| ******************************************************************************/ |
| |
| package org.eclipse.jst.common.project.facet.core; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.ProjectScope; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Path; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.preferences.IEclipsePreferences; |
| import org.eclipse.jdt.core.IClasspathEntry; |
| import org.eclipse.jdt.core.IJavaProject; |
| import org.eclipse.jdt.core.JavaCore; |
| import org.eclipse.osgi.util.NLS; |
| import org.eclipse.wst.common.project.facet.core.IFacetedProject; |
| import org.eclipse.wst.common.project.facet.core.IProjectFacetVersion; |
| import org.eclipse.wst.common.project.facet.core.ProjectFacetsManager; |
| import org.eclipse.wst.common.project.facet.core.runtime.IRuntime; |
| import org.osgi.service.prefs.BackingStoreException; |
| import org.osgi.service.prefs.Preferences; |
| |
| /** |
| * <p>A utility used in conjunction with IClasspathProvider in order to manage |
| * the bookkeeping when project facets are installed and uninstalled, and when |
| * the bound runtime changes. This utility tracks which classpath entries were |
| * added to the project by which facet and stores this information in a project |
| * metadata file. This enables the classpath entries to be removed without |
| * knowing what they are. It is only necessary to know which facet added them. |
| * This utility supports the case where the same classpath entry is added by |
| * multiple project facets. In this situation, a classpath entry is only |
| * removed when the removal has been requested for all of the facets that added |
| * it.</p> |
| * |
| * <p>Typically the project facet author will write something like this in the |
| * install delegate:</p> |
| * |
| * <pre> |
| * if( ! ClasspathHelper.addClasspathEntries( project, fv ) |
| * { |
| * // Handle the case when there is no bound runtime or when the bound |
| * // runtime cannot provide classpath entries for this facet. |
| * |
| * final List alternate = ...; |
| * ClasspathHelper.addClasspathEntries( project, fv, alternate ); |
| * } |
| * </pre> |
| * |
| * <p>And something like this in the uninstall delegate:</p> |
| * |
| * <pre> |
| * ClasspathHelper.removeClasspathEntries( project, fv ); |
| * </pre> |
| * |
| * <p>And something like this in the runtime changed delegate:</p> |
| * |
| * <pre> |
| * ClasspathHelper.removeClasspathEntries( project, fv ); |
| * |
| * if( ! ClasspathHelper.addClasspathEntries( project, fv ) |
| * { |
| * // Handle the case when there is no bound runtime or when the bound |
| * // runtime cannot provide classpath entries for this facet. |
| * |
| * final List alternate = ...; |
| * ClasspathHelper.addClasspathEntries( project, fv, alternate ); |
| * } |
| * </pre> |
| * |
| * <p>And something like this in the version change delegate:</p> |
| * |
| * <pre> |
| * final IProjectFacetVersion oldver |
| * = fproj.getInstalledVersion( fv.getProjectFacet() ); |
| * |
| * ClasspathHelper.removeClasspathEntries( project, oldver ); |
| * |
| * if( ! ClasspathHelper.addClasspathEntries( project, fv ) |
| * { |
| * // Handle the case when there is no bound runtime or when the bound |
| * // runtime cannot provide classpath entries for this facet. |
| * |
| * final List alternate = ...; |
| * ClasspathHelper.addClasspathEntries( project, fv, alternate ); |
| * } |
| * </pre> |
| * |
| * @author <a href="mailto:kosta@bea.com">Konstantin Komissarchik</a> |
| */ |
| |
| public final class ClasspathHelper |
| { |
| private static final String PLUGIN_ID |
| = "org.eclipse.jst.common.project.facet.core"; |
| |
| private static final Object SYSTEM_OWNER = new Object(); |
| |
| private ClasspathHelper() {} |
| |
| /** |
| * Convenience method for adding to the project the classpath entries |
| * provided for the specified project facet by the runtime bound to the |
| * project. The entries are marked as belonging to the specified project |
| * facet. |
| * |
| * @param project the project |
| * @param fv the project facet version that will own these entries |
| * @return <code>true</code> if classpath entries were added, or |
| * <code>false</code> if there is no runtime bound to the project or if |
| * it cannot provide classpath entries for the specified facet |
| * @throws CoreException if failed while adding the classpath entries |
| */ |
| |
| public static boolean addClasspathEntries( final IProject project, |
| final IProjectFacetVersion fv ) |
| |
| throws CoreException |
| |
| { |
| final IFacetedProject fproj |
| = ProjectFacetsManager.create( project ); |
| |
| final IRuntime runtime = fproj.getPrimaryRuntime(); |
| |
| if( runtime != null ) |
| { |
| final IClasspathProvider cpprov |
| = (IClasspathProvider) runtime.getAdapter( IClasspathProvider.class ); |
| |
| final List cpentries = cpprov.getClasspathEntries( fv ); |
| |
| if( cpentries != null ) |
| { |
| addClasspathEntries( project, fv, cpentries ); |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Add the provided classpath entries to project and marks them as belonging |
| * to the specified project facet. |
| * |
| * @param project the project |
| * @param fv the project facet version that will own these entries |
| * @param cpentries the classpath entries (element type: |
| * {@see IClasspathEntry}) |
| * @throws CoreException if failed while adding the classpath entries |
| */ |
| |
| public static void addClasspathEntries( final IProject project, |
| final IProjectFacetVersion fv, |
| final List cpentries ) |
| |
| throws CoreException |
| |
| { |
| try |
| { |
| final IJavaProject jproj = JavaCore.create( project ); |
| final List cp = getClasspath( jproj ); |
| boolean cpchanged = false; |
| |
| final Map prefs = readPreferences( project ); |
| |
| for( Iterator itr = cpentries.iterator(); itr.hasNext(); ) |
| { |
| final IClasspathEntry cpentry = (IClasspathEntry) itr.next(); |
| final IPath path = cpentry.getPath(); |
| |
| final boolean contains = cp.contains( cpentry ); |
| |
| Set owners = (Set) prefs.get( path ); |
| |
| if( owners == null ) |
| { |
| owners = new HashSet(); |
| |
| if( contains ) |
| { |
| owners.add( SYSTEM_OWNER ); |
| } |
| |
| prefs.put( path, owners ); |
| } |
| |
| owners.add( fv ); |
| |
| if( ! contains ) |
| { |
| cp.add( cpentry ); |
| cpchanged = true; |
| } |
| } |
| |
| if( cpchanged ) |
| { |
| setClasspath( jproj, cp ); |
| } |
| |
| writePreferences( project, prefs ); |
| } |
| catch( BackingStoreException e ) |
| { |
| final IStatus st |
| = new Status( IStatus.ERROR, PLUGIN_ID, 0, |
| Resources.failedWritingPreferences, e ); |
| |
| throw new CoreException( st ); |
| } |
| } |
| |
| /** |
| * Removes the classpath entries belonging to the specified project facet. |
| * Any entries that also belong to another facet are left in place. |
| * |
| * @param project the project |
| * @param fv the project facet that owns the entries that should be removed |
| * @throws CoreException if failed while removing classpath entries |
| */ |
| |
| public static void removeClasspathEntries( final IProject project, |
| final IProjectFacetVersion fv ) |
| |
| throws CoreException |
| |
| { |
| try |
| { |
| final IJavaProject jproj = JavaCore.create( project ); |
| final List cp = getClasspath( jproj ); |
| boolean cpchanged = false; |
| |
| final Map prefs = readPreferences( project ); |
| |
| for( Iterator itr1 = prefs.entrySet().iterator(); itr1.hasNext(); ) |
| { |
| final Map.Entry entry = (Map.Entry) itr1.next(); |
| final IPath path = (IPath) entry.getKey(); |
| final Set owners = (Set) entry.getValue(); |
| |
| if( owners.contains( fv ) ) |
| { |
| owners.remove( fv ); |
| |
| if( owners.size() == 0 ) |
| { |
| itr1.remove(); |
| |
| for( Iterator itr2 = cp.iterator(); itr2.hasNext(); ) |
| { |
| final IClasspathEntry cpentry |
| = (IClasspathEntry) itr2.next(); |
| |
| if( cpentry.getPath().equals( path ) ) |
| { |
| itr2.remove(); |
| cpchanged = true; |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| if( cpchanged ) |
| { |
| setClasspath( jproj, cp ); |
| } |
| |
| writePreferences( project, prefs ); |
| } |
| catch( BackingStoreException e ) |
| { |
| final IStatus st |
| = new Status( IStatus.ERROR, PLUGIN_ID, 0, |
| Resources.failedWritingPreferences, e ); |
| |
| throw new CoreException( st ); |
| } |
| } |
| |
| private static List getClasspath( final IJavaProject jproj ) |
| |
| throws CoreException |
| |
| { |
| final ArrayList list = new ArrayList(); |
| final IClasspathEntry[] cp = jproj.getRawClasspath(); |
| |
| for( int i = 0; i < cp.length; i++ ) |
| { |
| list.add( cp[ i ] ); |
| } |
| |
| return list; |
| } |
| |
| private static void setClasspath( final IJavaProject jproj, |
| final List cp ) |
| |
| throws CoreException |
| |
| { |
| final IClasspathEntry[] newcp |
| = (IClasspathEntry[]) cp.toArray( new IClasspathEntry[ cp.size() ] ); |
| |
| jproj.setRawClasspath( newcp, null ); |
| } |
| |
| private static Map readPreferences( final IProject project ) |
| |
| throws BackingStoreException |
| |
| { |
| final Preferences root = getPreferencesNode( project ); |
| final Map result = new HashMap(); |
| |
| final String[] keys = root.childrenNames(); |
| |
| for( int i = 0; i < keys.length; i++ ) |
| { |
| final String key = keys[ i ]; |
| final Preferences node = root.node( key ); |
| |
| final String owners = node.get( "owners", null ); |
| final String[] split = owners.split( ";" ); |
| final Set set = new HashSet(); |
| |
| for( int j = 0; j < split.length; j++ ) |
| { |
| final String segment = split[ j ]; |
| |
| if( segment.equals( "#system#" ) ) |
| { |
| set.add( SYSTEM_OWNER ); |
| } |
| else |
| { |
| final IProjectFacetVersion fv |
| = parseFeatureVersion( segment ); |
| |
| set.add( fv ); |
| } |
| } |
| |
| result.put( decode( key ), set ); |
| } |
| |
| return result; |
| } |
| |
| private static void writePreferences( final IProject project, |
| final Map prefs ) |
| |
| throws BackingStoreException |
| |
| { |
| final Preferences root = getPreferencesNode( project ); |
| final String[] children = root.childrenNames(); |
| |
| for( int i = 0; i < children.length; i++ ) |
| { |
| root.node( children[ i ] ).removeNode(); |
| } |
| |
| for( Iterator itr1 = prefs.entrySet().iterator(); itr1.hasNext(); ) |
| { |
| final Map.Entry entry = (Map.Entry) itr1.next(); |
| final IPath path = (IPath) entry.getKey(); |
| final Set owners = (Set) entry.getValue(); |
| |
| final StringBuffer buf = new StringBuffer(); |
| |
| for( Iterator itr2 = owners.iterator(); itr2.hasNext(); ) |
| { |
| final Object owner = itr2.next(); |
| |
| if( buf.length() > 0 ) |
| { |
| buf.append( ';' ); |
| } |
| |
| if( owner == SYSTEM_OWNER ) |
| { |
| buf.append( "#system#" ); |
| } |
| else |
| { |
| final IProjectFacetVersion fv |
| = (IProjectFacetVersion) owner; |
| |
| buf.append( fv.getProjectFacet().getId() ); |
| buf.append( ':' ); |
| buf.append( fv.getVersionString() ); |
| } |
| } |
| |
| final Preferences node = root.node( encode( path ) ); |
| node.put( "owners", buf.toString() ); |
| } |
| |
| root.flush(); |
| } |
| |
| |
| private static Preferences getPreferencesNode( final IProject project ) |
| { |
| final ProjectScope scope = new ProjectScope( project ); |
| final IEclipsePreferences pluginRoot = scope.getNode( PLUGIN_ID ); |
| return pluginRoot.node( "classpath.helper" ); |
| } |
| |
| private static IProjectFacetVersion parseFeatureVersion( final String str ) |
| { |
| final int colon = str.indexOf( ':' ); |
| final String id = str.substring( 0, colon ); |
| final String ver = str.substring( colon + 1 ); |
| |
| return ProjectFacetsManager.getProjectFacet( id ).getVersion( ver ); |
| } |
| |
| private static String encode( final IPath path ) |
| { |
| return path.toString().replaceAll( "/", "::" ); |
| } |
| |
| private static IPath decode( final String path ) |
| { |
| return new Path( path.replaceAll( "::", "/" ) ); |
| } |
| |
| private static final class Resources |
| |
| extends NLS |
| |
| { |
| public static String failedWritingPreferences; |
| |
| static |
| { |
| initializeMessages( ClasspathHelper.class.getName(), |
| Resources.class ); |
| } |
| } |
| |
| } |