    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one
    3    * or more contributor license agreements.  See the NOTICE file
    4    * distributed with this work for additional information
    5    * regarding copyright ownership.  The ASF licenses this file
    6    * to you under the Apache License, Version 2.0 (the
    7    * "License"); you may not use this file except in compliance
    8    * with the License.  You may obtain a copy of the License at
    9    *
    10   *   http://www.apache.org/licenses/LICENSE-2.0
    11   *
    12   * Unless required by applicable law or agreed to in writing,
    13   * software distributed under the License is distributed on an
    14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    15   * KIND, either express or implied.  See the License for the
    16   * specific language governing permissions and limitations
    17   * under the License.
    18   */
    19  package org.apache.felix.bundleplugin;
    20
    21
    22  import java.util.Collection;
    23  import java.util.Iterator;
    24  import java.util.LinkedHashSet;
    25  import java.util.Map;
    26  import java.util.regex.Pattern;
    27
    28  import org.apache.maven.artifact.Artifact;
    29  import org.apache.maven.plugin.MojoExecutionException;
    30
    31  import aQute.bnd.osgi.Instruction;
    32  import aQute.bnd.header.OSGiHeader;
    33
    34
    35  /**
    36   * Apply clause-based filter over given dependencies
    37   *
    38   * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
    39   */
    40  public abstract class AbstractDependencyFilter
    41  {
    42      private static final Pattern MISSING_KEY_PATTERN = Pattern.compile( "(^|,)\\p{Blank}*(!)?\\p{Blank}*([a-zA-Z]+=)" );
    43
    44      /**
    45       * Dependency artifacts.
    46       */
    47      private final Collection m_dependencyArtifacts;
    48
    49
    50      public AbstractDependencyFilter( Collection dependencyArtifacts )
    51      {
    52          m_dependencyArtifacts = dependencyArtifacts;
    53      }
    54
    55      private static abstract class DependencyFilter
    56      {
    57          private final Instruction m_instruction;
    58          private final String m_defaultValue;
    59
    60
    61          public DependencyFilter( String expression )
    62          {
    63              this( expression, "" );
    64          }
    65
    66
    67          public DependencyFilter( String expression, String defaultValue )
    68          {
    69              m_instruction = new Instruction( expression );
    70              m_defaultValue = defaultValue;
    71          }
    72
    73
    74          public void filter( Collection dependencies )
    75          {
    76              for ( Iterator i = dependencies.iterator(); i.hasNext(); )
    77              {
    78                  if ( false == matches( ( Artifact ) i.next() ) )
    79                  {
    80                      i.remove();
    81                  }
    82              }
    83          }
    84
    85
    86          abstract boolean matches( Artifact dependency );
    87
    88
    89          boolean matches( String text )
    90          {
    91              boolean result;
    92
    93              if ( null == text )
    94              {
    95                  result = m_instruction.matches( m_defaultValue );
    96              }
    97              else
    98              {
    99                  result = m_instruction.matches( text );
    100             }
    101
    102             return m_instruction.isNegated() ? !result : result;
    103         }
    104     }
    105
    106
    107     protected final void processInstructions( String header ) throws MojoExecutionException
    108     {
    109         Map instructions = OSGiHeader.parseHeader( MISSING_KEY_PATTERN.matcher( header ).replaceAll( "$1$2*;$3" ) );
    110
    111         Collection availableDependencies = new LinkedHashSet( m_dependencyArtifacts );
    112
    113         DependencyFilter filter;
    114         for ( Iterator clauseIterator = instructions.entrySet().iterator(); clauseIterator.hasNext(); )
    115         {
    116             String inline = "false";
    117
    118             // always start with a fresh *modifiable* collection for each unique clause
    119             Collection filteredDependencies = new LinkedHashSet( availableDependencies );
    120
    121             // CLAUSE: REGEXP --> { ATTRIBUTE MAP }
    122             Map.Entry clause = ( Map.Entry ) clauseIterator.next();
    123             String primaryKey = ( ( String ) clause.getKey() ).replaceFirst( "~+$", "" );
    124             boolean isNegative = primaryKey.startsWith( "!" );
    125             if ( isNegative )
    126             {
    127                 primaryKey = primaryKey.substring( 1 );
    128             }
    129
    130             if ( !"*".equals( primaryKey ) )
    131             {
    132                 filter = new DependencyFilter( primaryKey )
    133                 {
    134                     boolean matches( Artifact dependency )
    135                     {
    136                         return super.matches( dependency.getArtifactId() );
    137                     }
    138                 };
    139                 // FILTER ON MAIN CLAUSE
    140                 filter.filter( filteredDependencies );
    141             }
    142
    143             for ( Iterator attrIterator = ( ( Map ) clause.getValue() ).entrySet().iterator(); attrIterator.hasNext(); )
    144             {
    145                 // ATTRIBUTE: KEY --> REGEXP
    146                 Map.Entry attr = ( Map.Entry ) attrIterator.next();
    147                 if ( "groupId".equals( attr.getKey() ) )
    148                 {
    149                     filter = new DependencyFilter( ( String ) attr.getValue() )
    150                     {
    151                         boolean matches( Artifact dependency )
    152                         {
    153                             return super.matches( dependency.getGroupId() );
    154                         }
    155                     };
    156                 }
    157                 else if ( "artifactId".equals( attr.getKey() ) )
    158                 {
    159                     filter = new DependencyFilter( ( String ) attr.getValue() )
    160                     {
    161                         boolean matches( Artifact dependency )
    162                         {
    163                             return super.matches( dependency.getArtifactId() );
    164                         }
    165                     };
    166                 }
    167                 else if ( "version".equals( attr.getKey() ) )
    168                 {
    169                     filter = new DependencyFilter( ( String ) attr.getValue() )
    170                     {
    171                         boolean matches( Artifact dependency )
    172                         {
    173                             try
    174                             {
    175                                 // use the symbolic version if available (ie. 1.0.0-SNAPSHOT)
    176                                 return super.matches( dependency.getSelectedVersion().toString() );
    177                             }
    178                             catch ( Exception e )
    179                             {
    180                                 return super.matches( dependency.getVersion() );
    181                             }
    182                         }
    183                     };
    184                 }
    185                 else if ( "scope".equals( attr.getKey() ) )
    186                 {
    187                     filter = new DependencyFilter( ( String ) attr.getValue(), "compile" )
    188                     {
    189                         boolean matches( Artifact dependency )
    190                         {
    191                             return super.matches( dependency.getScope() );
    192                         }
    193                     };
    194                 }
    195                 else if ( "type".equals( attr.getKey() ) )
    196                 {
    197                     filter = new DependencyFilter( ( String ) attr.getValue(), "jar" )
    198                     {
    199                         boolean matches( Artifact dependency )
    200                         {
    201                             return super.matches( dependency.getType() );
    202                         }
    203                     };
    204                 }
    205                 else if ( "classifier".equals( attr.getKey() ) )
    206                 {
    207                     filter = new DependencyFilter( ( String ) attr.getValue() )
    208                     {
    209                         boolean matches( Artifact dependency )
    210                         {
    211                             return super.matches( dependency.getClassifier() );
    212                         }
    213                     };
    214                 }
    215                 else if ( "optional".equals( attr.getKey() ) )
    216                 {
    217                     filter = new DependencyFilter( ( String ) attr.getValue(), "false" )
    218                     {
    219                         boolean matches( Artifact dependency )
    220                         {
    221                             return super.matches( "" + dependency.isOptional() );
    222                         }
    223                     };
    224                 }
    225                 else if ( "inline".equals( attr.getKey() ) )
    226                 {
    227                     inline = ( String ) attr.getValue();
    228                     continue;
    229                 }
    230                 else
    231                 {
    232                     throw new MojoExecutionException( "Unexpected attribute " + attr.getKey() );
    233                 }
    234
    235                 // FILTER ON EACH ATTRIBUTE
    236                 filter.filter( filteredDependencies );
    237             }
    238
    239             if ( isNegative )
    240             {
    241                 // negative clauses reduce the set of available artifacts
    242                 availableDependencies.removeAll( filteredDependencies );
    243                 if ( !clauseIterator.hasNext() )
    244                 {
    245                     // assume there's an implicit * missing at the end
    246                     processDependencies( availableDependencies, inline );
    247                 }
    248             }
    249             else
    250             {
    251                 // positive clause; doesn't alter the available artifacts
    252                 processDependencies( filteredDependencies, inline );
    253             }
    254         }
    255     }
    256
    257
    258     protected abstract void processDependencies( Collection dependencies, String inline );
    259 }