001package org.apache.maven.plugins.enforcer.utils;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *  http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import org.apache.maven.artifact.Artifact;
023import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
024import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
025import org.apache.maven.artifact.versioning.VersionRange;
026import org.apache.maven.plugins.enforcer.AbstractVersionEnforcer;
027import java.util.Collection;
028import java.util.LinkedList;
029
030/**
031 * This class is used for matching Artifacts against a list of patterns.
032 * 
033 * @author Jakub Senko
034 * @see org.apache.maven.plugins.enforcer.BanTransitiveDependencies
035 */
036public final class ArtifactMatcher
037{
038
039    /**
040     * @author I don't know
041     */
042    public static class Pattern
043    {
044        private String pattern;
045
046        private String[] parts;
047
048        public Pattern( String pattern )
049        {
050            if ( pattern == null )
051            {
052                throw new NullPointerException( "pattern" );
053            }
054
055            this.pattern = pattern;
056
057            parts = pattern.split( ":", 7 );
058
059            if ( parts.length == 7 )
060            {
061                throw new IllegalArgumentException( "Pattern contains too many delimiters." );
062            }
063
064            for ( String part : parts )
065            {
066                if ( "".equals( part ) )
067                {
068                    throw new IllegalArgumentException( "Pattern or its part is empty." );
069                }
070            }
071        }
072
073        public boolean match( Artifact artifact )
074            throws InvalidVersionSpecificationException
075        {
076            if ( artifact == null )
077            {
078                throw new NullPointerException( "artifact" );
079            }
080
081            switch ( parts.length )
082            {
083                case 6:
084                    String classifier = artifact.getClassifier();
085                    if ( !matches( parts[5], classifier ) )
086                    {
087                        return false;
088                    }
089                case 5:
090                    String scope = artifact.getScope();
091                    if ( scope == null || scope.equals( "" ) )
092                    {
093                        scope = Artifact.SCOPE_COMPILE;
094                    }
095
096                    if ( !matches( parts[4], scope ) )
097                    {
098                        return false;
099                    }
100                case 4:
101                    String type = artifact.getType();
102                    if ( type == null || type.equals( "" ) )
103                    {
104                        type = "jar";
105                    }
106
107                    if ( !matches( parts[3], type ) )
108                    {
109                        return false;
110                    }
111
112                case 3:
113                    if ( !matches( parts[2], artifact.getVersion() ) )
114                    {
115                        // CHECKSTYLE_OFF: LineLength
116                        if ( !AbstractVersionEnforcer.containsVersion( VersionRange.createFromVersionSpec( parts[2] ),
117                                                                       new DefaultArtifactVersion(
118                                                                                                   artifact.getVersion() ) ) )
119                        // CHECKSTYLE_ON: LineLength
120                        {
121                            return false;
122                        }
123                    }
124
125                case 2:
126                    if ( !matches( parts[1], artifact.getArtifactId() ) )
127                    {
128                        return false;
129                    }
130                case 1:
131                    return matches( parts[0], artifact.getGroupId() );
132                default:
133                    throw new AssertionError();
134            }
135        }
136
137        private boolean matches( String expression, String input )
138        {
139            String regex =
140                    expression.replace( ".", "\\." ).replace( "*", ".*" ).replace( ":", "\\:" ).replace( '?', '.' )
141                            .replace( "[", "\\[" ).replace( "]", "\\]" ).replace( "(", "\\(" ).replace( ")", "\\)" );
142
143            // TODO: Check if this can be done better or prevented earlier.
144            if ( input == null )
145            {
146                input = "";
147            }
148
149            return java.util.regex.Pattern.matches( regex, input );
150        }
151
152        @Override
153        public String toString()
154        {
155            return pattern;
156        }
157    }
158
159    private Collection<Pattern> patterns = new LinkedList<>();
160
161    private Collection<Pattern> ignorePatterns = new LinkedList<>();
162
163    /**
164     * Construct class by providing patterns as strings. Empty strings are ignored.
165     *
166     * @param patterns includes
167     * @param ignorePatterns excludes
168     * @throws NullPointerException if any of the arguments is null
169     */
170    public ArtifactMatcher( final Collection<String> patterns, final Collection<String> ignorePatterns )
171    {
172        if ( patterns == null )
173        {
174            throw new NullPointerException( "patterns" );
175        }
176        if ( ignorePatterns == null )
177        {
178            throw new NullPointerException( "ignorePatterns" );
179        }
180        for ( String pattern : patterns )
181        {
182            if ( pattern != null && !"".equals( pattern ) )
183            {
184                this.patterns.add( new Pattern( pattern ) );
185            }
186        }
187
188        for ( String ignorePattern : ignorePatterns )
189        {
190            if ( ignorePattern != null && !"".equals( ignorePattern ) )
191            {
192                this.ignorePatterns.add( new Pattern( ignorePattern ) );
193            }
194        }
195    }
196
197    /**
198     * Check if artifact matches patterns.
199     * 
200     * @param artifact the artifact to match
201     * @return {@code true} if artifact matches any {@link #patterns} and none of the {@link #ignorePatterns}, otherwise
202     *         {@code false}
203     * @throws InvalidVersionSpecificationException if any pattern contains an invalid version range
204     */
205    public boolean match( Artifact artifact )
206        throws InvalidVersionSpecificationException
207    {
208        for ( Pattern pattern : patterns )
209        {
210            if ( pattern.match( artifact ) )
211            {
212                for ( Pattern ignorePattern : ignorePatterns )
213                {
214                    if ( ignorePattern.match( artifact ) )
215                    {
216                        return false;
217                    }
218                }
219                return true;
220            }
221        }
222        return false;
223    }
224}