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 }