| /****************************************************************************** |
| * 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.internal; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Set; |
| |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.MultiStatus; |
| import org.eclipse.osgi.util.NLS; |
| import org.eclipse.wst.common.project.facet.core.IConstraint; |
| import org.eclipse.wst.common.project.facet.core.IGroup; |
| 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.IVersionExpr; |
| |
| /** |
| * @author <a href="mailto:konstantin.komissarchik@oracle.com">Konstantin Komissarchik</a> |
| */ |
| |
| public final class Constraint |
| |
| implements IConstraint |
| |
| { |
| private final IProjectFacetVersion fv; |
| private final Type type; |
| private final List<Object> operands; |
| |
| Constraint( final IProjectFacetVersion fv, |
| final Type type, |
| final Object[] operands ) |
| { |
| this.fv = fv; |
| this.type = type; |
| |
| final List<Object> temp = new ArrayList<Object>(); |
| |
| for( int i = 0; i < operands.length; i++ ) |
| { |
| temp.add( operands[ i ] ); |
| } |
| |
| this.operands = Collections.unmodifiableList( temp ); |
| } |
| |
| public Type getType() |
| { |
| return this.type; |
| } |
| |
| public List<Object> getOperands() |
| { |
| return this.operands; |
| } |
| |
| public Object getOperand( final int index ) |
| { |
| return this.operands.get( index ); |
| } |
| |
| public IStatus check( final Collection<IProjectFacetVersion> facets ) |
| { |
| return check( facets, false ); |
| } |
| |
| public IStatus check( final Collection<IProjectFacetVersion> facets, |
| final boolean validateSoftDeps ) |
| { |
| final MultiStatus result = createMultiStatus(); |
| |
| if( this.type == Type.AND ) |
| { |
| for( Object operand : this.operands ) |
| { |
| final IConstraint childConstraint = (IConstraint) operand; |
| final IStatus st = childConstraint.check( facets, validateSoftDeps ); |
| |
| if( st.getSeverity() != IStatus.OK ) |
| { |
| result.addAll( st ); |
| } |
| } |
| } |
| else if( this.type == Type.OR ) |
| { |
| boolean someBranchWorks = false; |
| |
| for( Object operand : this.operands ) |
| { |
| final IConstraint childConstraint = (IConstraint) operand; |
| |
| if( childConstraint.check( facets, validateSoftDeps ).isOK() ) |
| { |
| someBranchWorks = true; |
| break; |
| } |
| } |
| |
| if( ! someBranchWorks ) |
| { |
| ValidationProblem p = null; |
| |
| if( this.operands.size() == 2 && containsOnlyRequires() ) |
| { |
| final ProjectFacetRef[] frefs = new ProjectFacetRef[ 2 ]; |
| |
| for( int i = 0; i < 2; i++ ) |
| { |
| final IConstraint c = (IConstraint) this.operands.get( i ); |
| final IProjectFacet rf = (IProjectFacet) c.getOperand( 0 ); |
| final IVersionExpr vexpr = (IVersionExpr) c.getOperand( 1 ); |
| |
| frefs[ i ] = new ProjectFacetRef( rf, vexpr ); |
| } |
| |
| p = new ValidationProblem( ValidationProblem.Type.REQUIRES_ONE_OF_TWO, |
| this.fv, frefs[ 0 ], |
| frefs[ 1 ] ); |
| } |
| |
| if( p == null ) |
| { |
| p = new ValidationProblem( ValidationProblem.Type.COMPLEX, |
| this.fv ); |
| } |
| |
| result.add( p ); |
| } |
| } |
| else if( this.type == Type.REQUIRES ) |
| { |
| final Boolean soft |
| = ( (Boolean) this.operands.get( this.operands.size() - 1 ) ); |
| |
| if( ! soft.equals( Boolean.TRUE ) || validateSoftDeps ) |
| { |
| final Object firstOperand = this.operands.get( 0 ); |
| |
| if( firstOperand instanceof IGroup ) |
| { |
| final IGroup group = (IGroup) firstOperand; |
| |
| if( ! containsAny( facets, group.getMembers() ) ) |
| { |
| final ValidationProblem.Type ptype |
| = ValidationProblem.Type.REQUIRES_GROUP; |
| |
| final ValidationProblem problem |
| = new ValidationProblem( ptype, this.fv, group ); |
| |
| result.add( problem ); |
| } |
| } |
| else |
| { |
| final IProjectFacet rf = (IProjectFacet) firstOperand; |
| final IVersionExpr vexpr = (IVersionExpr) this.operands.get( 1 ); |
| |
| boolean found = false; |
| |
| for( IProjectFacetVersion fv : facets ) |
| { |
| if( fv.getProjectFacet() == rf ) |
| { |
| if( vexpr.check( fv ) ) |
| { |
| found = true; |
| } |
| |
| break; |
| } |
| } |
| |
| if( ! found ) |
| { |
| final ValidationProblem.Type ptype = ValidationProblem.Type.REQUIRES; |
| final ProjectFacetRef fref = new ProjectFacetRef( rf, vexpr ); |
| |
| final ValidationProblem problem |
| = new ValidationProblem( ptype, this.fv, fref ); |
| |
| result.add( problem ); |
| } |
| } |
| } |
| } |
| else if( this.type == Type.CONFLICTS ) |
| { |
| final Object firstOperand = this.operands.get( 0 ); |
| |
| if( firstOperand instanceof IGroup ) |
| { |
| final IGroup group = (IGroup) firstOperand; |
| |
| for( IProjectFacetVersion member : group.getMembers() ) |
| { |
| if( member.getProjectFacet() != this.fv.getProjectFacet() && |
| facets.contains( member ) ) |
| { |
| final ValidationProblem.Type t |
| = ValidationProblem.Type.CONFLICTS; |
| |
| final ValidationProblem problem |
| = new ValidationProblem( t, this.fv, member ); |
| |
| result.add( problem ); |
| } |
| } |
| } |
| else |
| { |
| final IProjectFacet f = (IProjectFacet) firstOperand; |
| |
| final IVersionExpr vexpr |
| = this.operands.size() == 2 ? (IVersionExpr) this.operands.get( 1 ) : null; |
| |
| for( IProjectFacetVersion fver : facets ) |
| { |
| if( fver.getProjectFacet() == f ) |
| { |
| if( vexpr == null || vexpr.check( fver ) ) |
| { |
| final ValidationProblem.Type t |
| = ValidationProblem.Type.CONFLICTS; |
| |
| final ValidationProblem problem |
| = new ValidationProblem( t, this.fv, fver ); |
| |
| result.add( problem ); |
| |
| break; |
| } |
| } |
| } |
| } |
| } |
| else |
| { |
| throw new IllegalStateException(); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Prunes the constraint by removing parts that are not relevant or satisfied by default |
| * in the context of the provided fixed facets. |
| * |
| * <ol> |
| * <li>An AND or an OR operator with no children is eliminated.</li> |
| * <li>An AND or an OR operator with one child is replaced by that child.</li> |
| * <li>A REQUIRES_FACET operator is eliminated if all applicable versions of |
| * that facet are incompatible with the fixed facets.</li> |
| * <li>A REQUIRED_FACET operator is eliminated if it will accept every version |
| * of a fixed facet.</li> |
| * <li>A CONFLICTS_WITH_FACET operator is eliminated if none of the matched versions |
| * of that facet are compatible with the fixed facets.</li> |
| * <li>A CONFLICTS_WITH_GROUP operator is eliminated if all members of the group |
| * (not counting this facet) are incompatible with the fixed facets.</li> |
| * </ol> |
| */ |
| |
| public static IConstraint pruneConstraint( final IProjectFacetVersion facet, |
| final Set<IProjectFacet> fixedFacets ) |
| { |
| return pruneConstraint( facet, facet.getConstraint(), fixedFacets ); |
| } |
| |
| private static IConstraint pruneConstraint( final IProjectFacetVersion facet, |
| final IConstraint constraint, |
| final Set<IProjectFacet> fixedFacets ) |
| { |
| final IConstraint.Type type = constraint.getType(); |
| |
| if( type == IConstraint.Type.AND || type == IConstraint.Type.OR ) |
| { |
| final List<IConstraint> newOperands = new ArrayList<IConstraint>(); |
| boolean changed = false; |
| |
| for( Object operand : constraint.getOperands() ) |
| { |
| final IConstraint newOperand |
| = pruneConstraint( facet, (IConstraint) operand, fixedFacets ); |
| |
| if( newOperand == null ) |
| { |
| changed = true; |
| } |
| else if( operand != newOperand ) |
| { |
| changed = true; |
| newOperands.add( newOperand ); |
| } |
| else |
| { |
| newOperands.add( (IConstraint) operand ); |
| } |
| } |
| |
| final int newOperandCount = newOperands.size(); |
| |
| if( newOperandCount == 0 ) |
| { |
| return null; |
| } |
| else if( newOperandCount == 1 ) |
| { |
| return newOperands.get( 0 ); |
| } |
| else if( changed ) |
| { |
| return new Constraint( ( (Constraint) constraint ).fv, type, |
| newOperands.toArray() ); |
| } |
| } |
| else if( type == IConstraint.Type.REQUIRES ) |
| { |
| final Object firstOperand = constraint.getOperand( 0 ); |
| |
| if( firstOperand instanceof IProjectFacet ) |
| { |
| final IProjectFacet f = (IProjectFacet) firstOperand; |
| final IVersionExpr vexpr = (IVersionExpr) constraint.getOperand( 1 ); |
| |
| int acceptableVersionCount = 0; |
| |
| for( IProjectFacetVersion fv : f.getVersions() ) |
| { |
| if( vexpr.check( fv ) && fv.isValidFor( fixedFacets ) ) |
| { |
| acceptableVersionCount++; |
| } |
| } |
| |
| if( acceptableVersionCount == 0 ) |
| { |
| return null; |
| } |
| |
| if( fixedFacets.contains( f ) && |
| f.getVersions().size() == acceptableVersionCount ) |
| { |
| return null; |
| } |
| } |
| } |
| else if( type == IConstraint.Type.CONFLICTS ) |
| { |
| final Object firstOperand = constraint.getOperand( 0 ); |
| |
| if( firstOperand instanceof IProjectFacet ) |
| { |
| final IProjectFacet f = (IProjectFacet) firstOperand; |
| final IVersionExpr vexpr = (IVersionExpr) constraint.getOperand( 1 ); |
| |
| boolean isValidForContext = false; |
| |
| for( IProjectFacetVersion fv : f.getVersions() ) |
| { |
| if( vexpr.check( fv ) && fv.isValidFor( fixedFacets ) ) |
| { |
| isValidForContext = true; |
| break; |
| } |
| } |
| |
| if( ! isValidForContext ) |
| { |
| return null; |
| } |
| } |
| else if( firstOperand instanceof IGroup ) |
| { |
| final IGroup group = (IGroup) firstOperand; |
| |
| boolean atLeastOneIsValid = false; |
| |
| for( IProjectFacetVersion member : group.getMembers() ) |
| { |
| if( member.getProjectFacet() != facet.getProjectFacet() && |
| member.isValidFor( fixedFacets ) ) |
| { |
| atLeastOneIsValid = true; |
| break; |
| } |
| } |
| |
| if( ! atLeastOneIsValid ) |
| { |
| return null; |
| } |
| } |
| } |
| |
| return constraint; |
| } |
| |
| private boolean containsOnlyRequires() |
| { |
| for( Object operand : this.operands ) |
| { |
| if( ( (IConstraint) operand ).getType() != Type.REQUIRES ) |
| { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| private static boolean containsAny( final Collection<? extends Object> a, |
| final Collection<? extends Object> b ) |
| { |
| for( Object x : a ) |
| { |
| if( b.contains( x ) ) |
| { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| static MultiStatus createMultiStatus() |
| { |
| return createMultiStatus( new IStatus[ 0 ] ); |
| } |
| |
| public static MultiStatus createMultiStatus( final IStatus[] children ) |
| { |
| return new MultiStatus( FacetCorePlugin.PLUGIN_ID, 0, children, |
| Resources.validationProblems, null ); |
| } |
| |
| public String toString() |
| { |
| final StringBuilder buf = new StringBuilder(); |
| |
| buf.append( this.type.name() ); |
| buf.append( "( " ); //$NON-NLS-1$ |
| |
| boolean isFirst = true; |
| |
| for( Object operand : this.operands ) |
| { |
| if( isFirst ) |
| { |
| isFirst = false; |
| } |
| else |
| { |
| buf.append( ", " ); //$NON-NLS-1$ |
| } |
| |
| buf.append( operand.toString() ); |
| } |
| |
| buf.append( " )" ); //$NON-NLS-1$ |
| |
| return buf.toString(); |
| } |
| |
| private static final class Resources |
| |
| extends NLS |
| |
| { |
| public static String validationProblems; |
| |
| static |
| { |
| initializeMessages( Constraint.class.getName(), |
| Resources.class ); |
| } |
| } |
| |
| } |