[223650] allowing testing for project facets that are not defined
diff --git a/plugins/org.eclipse.wst.common.project.facet.core/src/org/eclipse/wst/common/project/facet/core/FacetedProjectFramework.java b/plugins/org.eclipse.wst.common.project.facet.core/src/org/eclipse/wst/common/project/facet/core/FacetedProjectFramework.java
index 0318c94..61e2278 100644
--- a/plugins/org.eclipse.wst.common.project.facet.core/src/org/eclipse/wst/common/project/facet/core/FacetedProjectFramework.java
+++ b/plugins/org.eclipse.wst.common.project.facet.core/src/org/eclipse/wst/common/project/facet/core/FacetedProjectFramework.java
@@ -11,6 +11,8 @@
 
 package org.eclipse.wst.common.project.facet.core;
 
+import java.util.Set;
+
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.wst.common.project.facet.core.events.IFacetedProjectEvent;
@@ -20,6 +22,7 @@
 import org.eclipse.wst.common.project.facet.core.internal.FacetedProjectFrameworkImpl;
 import org.eclipse.wst.common.project.facet.core.internal.FacetedProjectNature;
 import org.eclipse.wst.common.project.facet.core.runtime.IRuntime;
+import org.eclipse.wst.common.project.facet.core.util.internal.VersionExpr2;
 import org.osgi.service.prefs.BackingStoreException;
 import org.osgi.service.prefs.Preferences;
 
@@ -98,6 +101,7 @@
      * 
      * @param project the project to check for the facet presence
      * @param fid the project facet id
+     * @return <code>true</code> if specified project has the given facet
      * @throws CoreException if failed while reading faceted project metadata
      */
 
@@ -113,7 +117,8 @@
     /**
      * <p>Determines whether the specified project facet is installed in the
      * provided project. Returns <code>false</code> if the project is not 
-     * accessible, the project is not faceted or the facet id is unrecognized.</p>
+     * accessible or the project is not faceted. Works even if the facet or
+     * the version that is being checked is not defined.</p>
      * 
      * <p>This method is explicitly designed to avoid activation of the Faceted
      * Project Framework if the project is not faceted. For the code that
@@ -128,6 +133,7 @@
      * @param fid the project facet id
      * @param vexpr the version match expression, or <code>null</code> to
      *   match any version
+     * @return <code>true</code> if specified project has the given facet
      * @throws CoreException if failed while reading faceted project metadata;
      *   if the version expression is invalid
      */
@@ -162,7 +168,28 @@
                         
                         if( fv != null )
                         {
-                            return f.getVersions( vexpr ).contains( fv );
+                            final VersionExpr2 expr = new VersionExpr2( vexpr );
+                            return expr.check( fv.getVersionString() );
+                        }
+                    }
+                }
+                else
+                {
+                    for( IProjectFacetVersion fv : fproj.getProjectFacets() )
+                    {
+                        final IProjectFacet f = fv.getProjectFacet();
+                        
+                        if( f.getId().equals( fid ) )
+                        {
+                            if( vexpr == null )
+                            {
+                                return true;
+                            }
+                            else
+                            {
+                                final VersionExpr2 expr = new VersionExpr2( vexpr );
+                                return expr.check( fv.getVersionString() );
+                            }
                         }
                     }
                 }
diff --git a/plugins/org.eclipse.wst.common.project.facet.core/src/org/eclipse/wst/common/project/facet/core/util/internal/VersionExpr2.java b/plugins/org.eclipse.wst.common.project.facet.core/src/org/eclipse/wst/common/project/facet/core/util/internal/VersionExpr2.java
new file mode 100644
index 0000000..c31b1f3
--- /dev/null
+++ b/plugins/org.eclipse.wst.common.project.facet.core/src/org/eclipse/wst/common/project/facet/core/util/internal/VersionExpr2.java
@@ -0,0 +1,625 @@
+/******************************************************************************
+ * Copyright (c) 2008 Oracle
+ * 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 implementation and ongoing maintenance
+ ******************************************************************************/
+
+package org.eclipse.wst.common.project.facet.core.util.internal;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.wst.common.project.facet.core.DefaultVersionComparator;
+import org.eclipse.wst.common.project.facet.core.IVersionExpr;
+import org.eclipse.wst.common.project.facet.core.internal.FacetCorePlugin;
+
+/**
+ * @author <a href="mailto:konstantin.komissarchik@oracle.com">Konstantin Komissarchik</a>
+ */
+
+public final class VersionExpr2
+{
+    private static final int SM1_START = 0;
+    private static final int SM1_PARSING_START_VERSION = 1;
+    private static final int SM1_PARSING_END_VERSION = 2;
+    private static final int SM1_FINISHED_RANGE_INCLUSIVE = 3;
+    private static final int SM1_FINISHED_RANGE_EXCLUSIVE = 4;
+    private static final int SM1_PARSING_WILDCARD = 5;
+    
+    private static final int SM2_VERSION_START = 0;
+    private static final int SM2_VERSION_CONTINUING = 1;
+    private static final int SM2_ESCAPE = 2;
+
+    private final List<ISubExpr> subexprs;
+    private final Comparator<String> comparator;
+    
+    public VersionExpr2( final String expr )
+    
+        throws CoreException
+        
+    {
+        this( expr, null );
+    }
+    
+    public VersionExpr2( final String expr,
+                         final Comparator<String> comparator )
+    
+        throws CoreException
+        
+    {
+        this.subexprs = new ArrayList<ISubExpr>();
+        this.comparator = ( comparator != null ? comparator : new DefaultVersionComparator() );
+        
+        int state = SM1_START;
+        Range range = null;
+        
+        for( MutableInteger position = new MutableInteger(); 
+             position.value < expr.length(); position.value++ )
+        {
+            final char ch = expr.charAt( position.value );
+            
+            switch( state )
+            {
+                case SM1_START:
+                {
+                    if( ch == '[' )
+                    {
+                        range = new Range();
+                        range.includesStartVersion = true;
+                        state = SM1_PARSING_START_VERSION;
+                    }
+                    else if( ch == '(' )
+                    {
+                        range = new Range();
+                        range.includesStartVersion = false;
+                        state = SM1_PARSING_START_VERSION;
+                    }
+                    else if( ch == '*' )
+                    {
+                        this.subexprs.add( new Wildcard() );
+                        state = SM1_PARSING_WILDCARD;
+                    }
+                    else if( ch == ' ' || ch == ',' )
+                    {
+                        // ignore
+                    }
+                    else
+                    {
+                        final StringBuffer buf = new StringBuffer();
+                        final int exitState = parseVersion( expr, position, buf );
+                        
+                        if( exitState == SM1_START )
+                        {
+                            final String vstr = buf.toString();
+                            final Range r = new Range();
+                            
+                            r.startVersion = parseVersion( vstr );
+                            r.includesStartVersion = true;
+                            r.endVersion = r.startVersion;
+                            r.includesEndVersion = true;
+                            
+                            this.subexprs.add( r );
+                        }
+                        else if( exitState == SM1_FINISHED_RANGE_INCLUSIVE ||
+                                 exitState == SM1_FINISHED_RANGE_EXCLUSIVE )
+                        {
+                            range = new Range();
+                            range.endVersion = parseVersion( buf.toString() );
+                            state = exitState;
+                            position.value--;
+                        }
+                        else
+                        {
+                            throw createInvalidVersionExprException( expr );
+                        }
+                    }
+                    
+                    break;
+                }
+                case SM1_PARSING_START_VERSION:
+                {
+                    final StringBuffer buf = new StringBuffer();
+                    final int exitState = parseVersion( expr, position, buf );
+
+                    if( exitState == SM1_START )
+                    {
+                        range.startVersion = parseVersion( buf.toString() );
+                        this.subexprs.add( range );
+                        range = null;
+                        state = exitState;
+                    }
+                    else if( exitState == SM1_PARSING_END_VERSION )
+                    {
+                        range.startVersion = parseVersion( buf.toString() );
+                        state = exitState;
+                    }
+                    else
+                    {
+                        throw createInvalidVersionExprException( expr );
+                    }
+                    
+                    break;
+                }
+                case SM1_PARSING_END_VERSION:
+                {
+                    final StringBuffer buf = new StringBuffer();
+                    final int exitState = parseVersion( expr, position, buf );
+                    
+                    if( exitState == SM1_FINISHED_RANGE_INCLUSIVE ||
+                        exitState == SM1_FINISHED_RANGE_EXCLUSIVE )
+                    {
+                        range.endVersion = parseVersion( buf.toString() );
+                        state = exitState;
+                        position.value--;
+                    }
+                    else
+                    {
+                        throw createInvalidVersionExprException( expr );
+                    }
+                    
+                    break;
+                }
+                case SM1_FINISHED_RANGE_INCLUSIVE:
+                case SM1_FINISHED_RANGE_EXCLUSIVE:
+                {
+                    range.includesEndVersion 
+                        = ( state == SM1_FINISHED_RANGE_INCLUSIVE );
+                    
+                    this.subexprs.add( range );
+                    range = null;
+                    
+                    state = SM1_START;
+                    
+                    break;
+                }
+                case SM1_PARSING_WILDCARD:
+                {
+                    if( ch == ' ' )
+                    {
+                        // ignore
+                    }
+                    else if( ch == ',' )
+                    {
+                        state = SM1_START;
+                    }
+                    else
+                    {
+                        throw createInvalidVersionExprException( expr );
+                    }
+                }
+                default:
+                {
+                    throw new IllegalStateException();
+                }
+            }
+        }
+    }
+    
+    private int parseVersion( final String str,
+                              final MutableInteger position,
+                              final StringBuffer version )
+    
+        throws CoreException
+        
+    {
+        int localState = SM2_VERSION_START;
+        int exitState = -1;
+        
+        for( ; exitState == -1 && position.value < str.length(); 
+             position.value++ )
+        {
+            final char ch = str.charAt( position.value );
+            
+            switch( localState )
+            {
+                case SM2_VERSION_START:
+                {
+                    if( ch == '[' || ch == '(' || ch == ']' || ch == ')' ||
+                        ch == '-' || ch == ',' )
+                    {
+                        throw createInvalidVersionExprException( str );
+                    }
+                    else if( ch == '\\' )
+                    {
+                        localState = SM2_ESCAPE;
+                    }
+                    else if( ch == ' ' )
+                    {
+                        // ignore
+                    }
+                    else
+                    {
+                        version.append( ch );
+                        localState = SM2_VERSION_CONTINUING;
+                    }
+                    
+                    break;
+                }
+                case SM2_VERSION_CONTINUING:
+                {
+                    if( ch == '[' || ch == '(' )
+                    {
+                        throw createInvalidVersionExprException( str );
+                    }
+                    else if( ch == '\\' )
+                    {
+                        localState = SM2_ESCAPE;
+                    }
+                    else if( ch == ',' )
+                    {
+                        exitState = SM1_START;
+                    }
+                    else if( ch == '-' )
+                    {
+                        exitState = SM1_PARSING_END_VERSION;
+                    }
+                    else if( ch == ']' )
+                    {
+                        exitState = SM1_FINISHED_RANGE_INCLUSIVE;
+                    }
+                    else if( ch == ')' )
+                    {
+                        exitState = SM1_FINISHED_RANGE_EXCLUSIVE;
+                    }
+                    else if( ch == ' ' )
+                    {
+                        // ignore
+                    }
+                    else
+                    {
+                        version.append( ch );
+                    }
+                    
+                    break;
+                }
+                case SM2_ESCAPE:
+                {
+                    version.append( ch );
+                    break;
+                }
+                default:
+                {
+                    throw new IllegalStateException();
+                }
+            }
+        }
+        
+        position.value--;
+        
+        if( exitState != -1 )
+        {
+            return exitState;
+        }
+        else
+        {
+            if( localState == SM2_VERSION_CONTINUING )
+            {
+                // Expected end of input.
+                
+                return SM1_START;
+            }
+            else
+            {
+                // Unexpected end of input.
+                
+                throw createInvalidVersionExprException( str );
+            }
+        }
+    }
+    
+    private Version parseVersion( final String str )
+    {
+        return new Version( str, this.comparator );
+    }
+    
+    public boolean check( final String ver )
+    {
+        final Version version = parseVersion( ver );
+        
+        for( ISubExpr subexpr : this.subexprs )
+        {
+            if( subexpr.evaluate( version ) )
+            {
+                return true;
+            }
+        }
+        
+        return false;
+    }
+    
+    public boolean isSingleVersionMatch()
+    {
+        if( this.subexprs.size() == 1 )
+        {
+            final ISubExpr subExpr = this.subexprs.get( 0 );
+            
+            if( subExpr instanceof Range )
+            {
+                final Range range = (Range) subExpr;
+                
+                if( range.startVersion.equals( range.endVersion ) &&
+                    range.includesStartVersion == range.includesEndVersion == true )
+                {
+                    return true;
+                }
+            }
+        }
+        
+        return false;
+    }
+
+    public boolean isSimpleAllowNewer()
+    {
+        if( this.subexprs.size() == 1 )
+        {
+            final ISubExpr subExpr = this.subexprs.get( 0 );
+            
+            if( subExpr instanceof Range )
+            {
+                final Range range = (Range) subExpr;
+            
+                if( range.startVersion != null && range.endVersion == null &&
+                    range.includesStartVersion  )
+                {
+                    return true;
+                }
+            }
+        }
+        
+        return false;
+    }
+    
+    public boolean isWildcard()
+    {
+        return this.subexprs.size() == 1 && 
+               this.subexprs.get( 0 ) instanceof Wildcard;
+    }
+    
+    public String getFirstVersion()
+    {
+        if( isSingleVersionMatch() || isSimpleAllowNewer() )
+        {
+            final Range range = (Range) this.subexprs.get( 0 );
+            return range.startVersion.toString();
+        }
+        else
+        {
+            throw new IllegalStateException();
+        }
+    }
+    
+    public String toString()
+    {
+        final StringBuffer buf = new StringBuffer();
+        
+        for( ISubExpr subexpr : this.subexprs )
+        {
+            if( buf.length() > 0 ) buf.append( ',' );
+            buf.append( subexpr.toString() );
+        }
+        
+        return buf.toString();
+    }
+    
+    public String toDisplayString()
+    {
+        if( this.subexprs.size() == 1 )
+        {
+            final ISubExpr subexpr = this.subexprs.get( 0 );
+            
+            if( subexpr instanceof Range )
+            {
+                final Range r = (Range) subexpr;
+                
+                if( r.isSingleVersion() )
+                {
+                    return r.startVersion.toString();
+                }
+                else if( r.endVersion == null && r.includesStartVersion )
+                {
+                    return NLS.bind( Resources.versionOrNewer, 
+                                     r.startVersion.toString() );
+                }
+            }
+        }
+
+        boolean onlySingleVersions = true;
+        
+        for( ISubExpr subexpr : this.subexprs )
+        {
+            if( ! ( subexpr instanceof Range ) || 
+                ! ( (Range) subexpr ).isSingleVersion() )
+            {
+                onlySingleVersions = false;
+                break;
+            }
+        }
+        
+        if( onlySingleVersions )
+        {
+            final StringBuffer buf = new StringBuffer();
+
+            for( Iterator<ISubExpr> itr = this.subexprs.iterator(); itr.hasNext(); )
+            {
+                final Range r = (Range) itr.next();
+                
+                if( buf.length() > 0 )
+                {
+                    if( itr.hasNext() )
+                    {
+                        buf.append( ", " ); //$NON-NLS-1$
+                    }
+                    else
+                    {
+                        buf.append( " or " ); //$NON-NLS-1$
+                    }
+                }
+                
+                buf.append( r.startVersion.toString() );
+            }
+            
+            return buf.toString();
+        }
+        
+        return toString();
+    }
+    
+    private static CoreException createInvalidVersionExprException( final String str )
+    {
+        final String msg
+            = NLS.bind( Resources.invalidVersionExpr, str );
+    
+        final IStatus st = FacetCorePlugin.createErrorStatus( msg );
+    
+        return new CoreException( st );
+    }
+    
+    private static interface ISubExpr
+    {
+        boolean evaluate( Version version );
+    }
+    
+    private static final class Range
+    
+        implements ISubExpr
+        
+    {
+        public Version startVersion = null;
+        public boolean includesStartVersion = false;
+        public Version endVersion = null;
+        public boolean includesEndVersion = false;
+        
+        public boolean isSingleVersion()
+        {
+            return this.startVersion.equals( this.endVersion ) &&
+                   this.includesStartVersion == this.includesEndVersion == true;
+        }
+        
+        public boolean evaluate( final Version version )
+        {
+            if( this.startVersion != null )
+            {
+                final int res = version.compareTo( this.startVersion );
+                
+                if( ! ( res > 0 || ( res == 0 && this.includesStartVersion ) ) )
+                {
+                    return false;
+                }
+            }
+            
+            if( this.endVersion != null )
+            {
+                final int res = version.compareTo( this.endVersion );
+                
+                if( ! ( res < 0 || ( res == 0 && this.includesEndVersion ) ) )
+                {
+                    return false;
+                }
+            }
+            
+            return true;
+        }
+        
+        public String toString()
+        {
+            if( this.startVersion.equals( this.endVersion ) &&
+                this.includesStartVersion == this.includesEndVersion == true )
+            {
+                return this.startVersion.toString();
+            }
+            else
+            {
+                final StringBuffer buf = new StringBuffer();
+                
+                if( this.startVersion != null )
+                {
+                    buf.append( this.includesStartVersion ? '[' : '(' );
+                    buf.append( this.startVersion.toString() );
+                }
+                
+                if( this.endVersion != null )
+                {
+                    if( buf.length() != 0 )
+                    {
+                        buf.append( '-' );
+                    }
+                    
+                    buf.append( this.endVersion.toString() );
+                    buf.append( this.includesEndVersion ? ']' : ')' );
+                }
+                
+                return buf.toString();
+            }
+        }
+    }
+    
+    private static final class Wildcard
+    
+        implements ISubExpr
+        
+    {
+        public boolean evaluate( final Version version )
+        {
+            return true;
+        }
+        
+        public String toString()
+        {
+            return IVersionExpr.WILDCARD_SYMBOL;
+        }
+    }
+    
+    private static final class MutableInteger
+    {
+        public int value = 0;
+    }
+    
+    private final class Version
+    {
+        private final String versionString;
+        private final Comparator<String> comparator;
+        
+        public Version( final String versionString,
+                        final Comparator<String> comparator )
+        {
+            this.versionString = versionString;
+            this.comparator = comparator;
+        }
+        
+        public int compareTo( final Object obj )
+        {
+            return this.comparator.compare( this.versionString, ( (Version) obj  ).toString() );
+        }
+
+        public String toString()
+        {
+            return this.versionString;
+        }
+    }
+    
+    private static final class Resources
+    
+        extends NLS
+        
+    {
+        public static String invalidVersionExpr;
+        public static String versionOrNewer;
+        
+        static
+        {
+            initializeMessages( VersionExpr2.class.getName(), 
+                                Resources.class );
+        }
+    }
+
+}
diff --git a/plugins/org.eclipse.wst.common.project.facet.core/src/org/eclipse/wst/common/project/facet/core/util/internal/VersionExpr2.properties b/plugins/org.eclipse.wst.common.project.facet.core/src/org/eclipse/wst/common/project/facet/core/util/internal/VersionExpr2.properties
new file mode 100644
index 0000000..e16639a
--- /dev/null
+++ b/plugins/org.eclipse.wst.common.project.facet.core/src/org/eclipse/wst/common/project/facet/core/util/internal/VersionExpr2.properties
@@ -0,0 +1,2 @@
+invalidVersionExpr = Version expression "{0}" is invalid.
+versionOrNewer = {0} or newer
\ No newline at end of file
diff --git a/tests/org.eclipse.wst.common.project.facet.core.tests/plugin.xml b/tests/org.eclipse.wst.common.project.facet.core.tests/plugin.xml
index 4fba5c6..297194a 100644
--- a/tests/org.eclipse.wst.common.project.facet.core.tests/plugin.xml
+++ b/tests/org.eclipse.wst.common.project.facet.core.tests/plugin.xml
@@ -565,6 +565,7 @@
 
     <project-facet id="pct-f2"/>
     <project-facet-version facet="pct-f2" version="1.0"/>
+    <project-facet-version facet="pct-f2" version="2.0"/>
     <action facet="pct-f2" type="INSTALL">
       <delegate class="org.eclipse.wst.common.project.facet.core.tests.support.NoOpDelegate"/>
     </action>
diff --git a/tests/org.eclipse.wst.common.project.facet.core.tests/src/org/eclipse/wst/common/project/facet/core/tests/ProjectCreationTests.java b/tests/org.eclipse.wst.common.project.facet.core.tests/src/org/eclipse/wst/common/project/facet/core/tests/ProjectCreationTests.java
index 0502e6b..d93cf7f 100644
--- a/tests/org.eclipse.wst.common.project.facet.core.tests/src/org/eclipse/wst/common/project/facet/core/tests/ProjectCreationTests.java
+++ b/tests/org.eclipse.wst.common.project.facet.core.tests/src/org/eclipse/wst/common/project/facet/core/tests/ProjectCreationTests.java
@@ -11,18 +11,24 @@
 
 package org.eclipse.wst.common.project.facet.core.tests;
 
+import static org.eclipse.wst.common.project.facet.core.tests.support.TestUtils.readFromFile;
+import static org.eclipse.wst.common.project.facet.core.tests.support.TestUtils.writeToFile;
+
 import java.io.IOException;
 
 import junit.framework.Test;
 import junit.framework.TestSuite;
 
+import org.eclipse.core.resources.IFile;
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.resources.IProjectDescription;
 import org.eclipse.core.runtime.CoreException;
+import org.eclipse.wst.common.project.facet.core.FacetedProjectFramework;
 import org.eclipse.wst.common.project.facet.core.IFacetedProject;
 import org.eclipse.wst.common.project.facet.core.IProjectFacet;
 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.internal.FacetedProject;
 import org.eclipse.wst.common.project.facet.core.tests.support.TestUtils;
 
 /**
@@ -42,6 +48,7 @@
 
     private static IProjectFacet f2;
     private static IProjectFacetVersion f2v1;
+    private static IProjectFacetVersion f2v2;
     
     static
     {
@@ -50,6 +57,7 @@
 
         f2 = ProjectFacetsManager.getProjectFacet( "pct-f2" );
         f2v1 = f2.getVersion( "1.0" );
+        f2v2 = f2.getVersion( "2.0" );
     }
 
     private ProjectCreationTests( final String name )
@@ -74,12 +82,14 @@
         suite.addTest( new ProjectCreationTests( "testWrapperCreation1" ) );
         suite.addTest( new ProjectCreationTests( "testWrapperCreation2" ) );
         suite.addTest( new ProjectCreationTests( "testWrapperCreation3" ) );
+        suite.addTest( new ProjectCreationTests( "testHasProjectFacet1" ) );
+        suite.addTest( new ProjectCreationTests( "testHasProjectFacet2" ) );
         
         return suite;
     }
 
     /**
-     * Tests {@see ProjectFacetsManager.create(String,IPath,IProgressMonitor)}
+     * Tests {@link ProjectFacetsManager.create(String,IPath,IProgressMonitor)}
      * method. In this scenario, there is no project with the same name.
      * 
      * @throws CoreException
@@ -110,7 +120,7 @@
     }
     
     /**
-     * Tests {@see ProjectFacetsManager.create(String,IPath,IProgressMonitor)}
+     * Tests {@link ProjectFacetsManager.create(String,IPath,IProgressMonitor)}
      * method. In this scenario, there is a faceted project with the same name.
      * 
      * @throws CoreException
@@ -139,7 +149,7 @@
     }
 
     /**
-     * Tests {@see ProjectFacetsManager.create(String,IPath,IProgressMonitor)}
+     * Tests {@link ProjectFacetsManager.create(String,IPath,IProgressMonitor)}
      * method. In this scenario, there is a non-faceted project with the same
      * name.
      * 
@@ -174,7 +184,7 @@
     }
 
     /**
-     * Tests {@see ProjectFacetsManager.create(String,IPath,IProgressMonitor)} method. In this 
+     * Tests {@link ProjectFacetsManager.create(String,IPath,IProgressMonitor)} method. In this 
      * scenario, there was previously a faceted project at the specified location. The test 
      * verifies that previously-installed facets are recognized after the project is resurrected.
      * 
@@ -208,7 +218,7 @@
     }
     
     /**
-     * Tests {@see ProjectFacetsManager.create(IProject,boolean,IProgressMonitor)}
+     * Tests {@link ProjectFacetsManager.create(IProject,boolean,IProgressMonitor)}
      * method. In this scenario project is not faceted and convertIfNecessary
      * is set to true.
      * 
@@ -241,7 +251,7 @@
     }
 
     /**
-     * Tests {@see ProjectFacetsManager.create(IProject,boolean,IProgressMonitor)}
+     * Tests {@link ProjectFacetsManager.create(IProject,boolean,IProgressMonitor)}
      * method. In this scenario project is faceted and convertIfNecessary
      * is set to true.
      * 
@@ -271,7 +281,7 @@
     }
     
     /**
-     * Tests {@see ProjectFacetsManager.create(IProject,boolean,IProgressMonitor)}
+     * Tests {@link ProjectFacetsManager.create(IProject,boolean,IProgressMonitor)}
      * method. In this scenario project is faceted and convertIfNecessary
      * is set to false.
      * 
@@ -301,7 +311,7 @@
     }
     
     /**
-     * Tests {@see ProjectFacetsManager.create(IProject,boolean,IProgressMonitor)}
+     * Tests {@link ProjectFacetsManager.create(IProject,boolean,IProgressMonitor)}
      * method. In this scenario project is not faceted and convertIfNecessary
      * is set to false.
      * 
@@ -328,7 +338,7 @@
     }
     
     /**
-     * Tests {@see ProjectFacetsManager.create(IProject)} method. This scenario
+     * Tests {@link ProjectFacetsManager.create(IProject)} method. This scenario
      * validates that the wrapper cache is working and the same instance is
      * returned when the create method is called multiple times.
      * 
@@ -354,7 +364,7 @@
     }
 
     /**
-     * Tests {@see ProjectFacetsManager.create(IProject)} method. In this
+     * Tests {@link ProjectFacetsManager.create(IProject)} method. In this
      * scenario, the input project does not exist.
      * 
      * @throws CoreException
@@ -370,7 +380,7 @@
     }
 
     /**
-     * Tests {@see ProjectFacetsManager.create(IProject)} method. In this
+     * Tests {@link ProjectFacetsManager.create(IProject)} method. In this
      * scenario, the input project is closed.
      * 
      * @throws CoreException
@@ -392,4 +402,101 @@
         assertNull( ProjectFacetsManager.create( proj ) );
     }
     
+    /**
+     * Tests the various methods that check whether a particular facet is present in a project:<br/><br/>
+     * 
+     * {@link IFacetedProject.hasProjecFacet(IProjectFacet)}<br/>
+     * {@link IFacetedProject.hasProjecFacet(IProjectFacet)}<br/>
+     * {@link FacetedProjectFramework.hasProjectFacet(IProject,String,String)}</br><br/>
+     * 
+     * In this scenario, the facets being tested are known to the framework.
+     * 
+     * @throws CoreException
+     */
+    
+    public void testHasProjectFacet1()
+    
+        throws CoreException
+        
+    {
+        final IFacetedProject fproj 
+            = ProjectFacetsManager.create( "abc", null, null );
+        
+        final IProject proj = fproj.getProject();
+        this.resourcesToCleanup.add( proj );
+        
+        fproj.installProjectFacet( f1v1, null, null );
+        fproj.installProjectFacet( f2v1, null, null );
+        
+        assertTrue( fproj.hasProjectFacet( f1 ) );
+        assertTrue( fproj.hasProjectFacet( f1v1 ) );
+
+        assertTrue( fproj.hasProjectFacet( f2 ) );
+        assertTrue( fproj.hasProjectFacet( f2v1 ) );
+        assertFalse( fproj.hasProjectFacet( f2v2 ) );
+        
+        assertTrue( FacetedProjectFramework.hasProjectFacet( proj, f1.getId() ) );
+        assertTrue( FacetedProjectFramework.hasProjectFacet( proj, f1.getId(), null ) );
+        assertTrue( FacetedProjectFramework.hasProjectFacet( proj, f1.getId(), "1.0" ) );
+        assertTrue( FacetedProjectFramework.hasProjectFacet( proj, f1.getId(), "[0.5-3.7]" ) );
+        assertFalse( FacetedProjectFramework.hasProjectFacet( proj, f1.getId(), "2.3" ) );
+        assertFalse( FacetedProjectFramework.hasProjectFacet( proj, f1.getId(), "[2.3-5.8)" ) );
+        
+        assertTrue( FacetedProjectFramework.hasProjectFacet( proj, f2.getId() ) );
+        assertTrue( FacetedProjectFramework.hasProjectFacet( proj, f2.getId(), null ) );
+        assertTrue( FacetedProjectFramework.hasProjectFacet( proj, f2.getId(), "1.0" ) );
+        assertTrue( FacetedProjectFramework.hasProjectFacet( proj, f2.getId(), "[0.5-3.7]" ) );
+        assertFalse( FacetedProjectFramework.hasProjectFacet( proj, f2.getId(), "2.3" ) );
+        assertFalse( FacetedProjectFramework.hasProjectFacet( proj, f2.getId(), "[2.3-5.8)" ) );
+    }
+    
+    /**
+     * Tests the various methods that check whether a particular facet is present in a project:<br/><br/>
+     * 
+     * {@link FacetedProjectFramework.hasProjectFacet(IProject,String,String)}</br><br/>
+     * 
+     * In this scenario, the facets being tested are not known to the framework. This comes up as part of
+     * upgrade and transition scenarios where facets previously installed are no longer defined.
+     * 
+     * @throws CoreException
+     */
+    
+    public void testHasProjectFacet2()
+    
+        throws CoreException, IOException
+        
+    {
+        final IFacetedProject fproj 
+            = ProjectFacetsManager.create( "abc", null, null );
+        
+        final IProject proj = fproj.getProject();
+        this.resourcesToCleanup.add( proj );
+        
+        fproj.installProjectFacet( f1v1, null, null );
+        fproj.installProjectFacet( f2v1, null, null );
+        
+        final IFile fpjMdFile = proj.getFile( FacetedProject.METADATA_FILE );
+        
+        String fpjMdFileContents = readFromFile( fpjMdFile );
+        fpjMdFileContents = fpjMdFileContents.replace( "pct-f1", "foo" );
+        fpjMdFileContents = fpjMdFileContents.replace( "<installed facet=\"pct-f2\" version=\"1.0\"/>", "<installed facet=\"pct-f2\" version=\"1.1\"/>" );
+        writeToFile( fpjMdFile, fpjMdFileContents );
+        
+        assertTrue( FacetedProjectFramework.hasProjectFacet( proj, "foo" ) );
+        assertTrue( FacetedProjectFramework.hasProjectFacet( proj, "foo", null ) );
+        assertTrue( FacetedProjectFramework.hasProjectFacet( proj, "foo", "1.0" ) );
+        assertTrue( FacetedProjectFramework.hasProjectFacet( proj, "foo", "[0.5-3.7]" ) );
+        assertFalse( FacetedProjectFramework.hasProjectFacet( proj, "foo", "2.3" ) );
+        assertFalse( FacetedProjectFramework.hasProjectFacet( proj, "foo", "[2.3-5.8)" ) );
+        
+        assertTrue( FacetedProjectFramework.hasProjectFacet( proj, f2.getId() ) );
+        assertTrue( FacetedProjectFramework.hasProjectFacet( proj, f2.getId(), null ) );
+        assertTrue( FacetedProjectFramework.hasProjectFacet( proj, f2.getId(), "1.1" ) );
+        assertTrue( FacetedProjectFramework.hasProjectFacet( proj, f2.getId(), "[0.5-3.7]" ) );
+        assertFalse( FacetedProjectFramework.hasProjectFacet( proj, f2.getId(), "2.3" ) );
+        assertFalse( FacetedProjectFramework.hasProjectFacet( proj, f2.getId(), "[2.3-5.8)" ) );
+
+        assertFalse( FacetedProjectFramework.hasProjectFacet( proj, "abc" ) );
+        assertFalse( FacetedProjectFramework.hasProjectFacet( proj, "abc", null ) );
+    }
 }