001package ball.maven.plugins.javadoc;
002/*-
003 * ##########################################################################
004 * Javadoc Maven Plugin
005 * %%
006 * Copyright (C) 2021, 2022 Allen D. Ball
007 * %%
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * 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, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 * ##########################################################################
020 */
021import java.net.MalformedURLException;
022import java.net.URL;
023import java.util.NoSuchElementException;
024import java.util.Set;
025import java.util.TreeMap;
026import java.util.regex.Matcher;
027import java.util.regex.Pattern;
028import lombok.Data;
029import lombok.extern.slf4j.Slf4j;
030import org.apache.commons.text.StringSubstitutor;
031import org.apache.commons.text.lookup.StringLookup;
032import org.apache.maven.artifact.Artifact;
033import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
034import org.apache.maven.shared.artifact.filter.StrictPatternIncludesArtifactFilter;
035
036import static java.util.Arrays.asList;
037import static java.util.stream.Collectors.toSet;
038
039/**
040 * {@code <link/>} parameter.
041 *
042 * {@bean.info}
043 *
044 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball}
045 */
046@Data @Slf4j
047public class Link {
048    private static final Pattern NUMBER = Pattern.compile("[0-9]+");
049    private static final Pattern DOT = Pattern.compile("[.]");
050
051    private String artifact = null;
052    private URL url = null;
053
054    /**
055     * See {@link StrictPatternIncludesArtifactFilter#include(Artifact)}.
056     */
057    public boolean include(Artifact artifact) {
058        ArtifactFilter filter = getArtifactFilter();
059
060        return filter != null && getArtifactFilter().include(artifact);
061    }
062
063    private ArtifactFilter getArtifactFilter() {
064        ArtifactFilter filter = null;
065
066        if (artifact != null) {
067            filter = new StrictPatternIncludesArtifactFilter(asList(artifact.split("[,\\p{Space}]+")));
068        }
069
070        return filter;
071    }
072
073    /**
074     * Method to return a {@link URL} substituting <code>{g}</code>,
075     * <code>{a}</code>, and <code>{v}</code> with
076     * {@link Artifact#getGroupId()}, {@link Artifact#getArtifactId()}, and
077     * {@link Artifact#getVersion()}, respectively.
078     *
079     * @param   artifact        The {@link Artifact}.
080     *
081     * @return  The {@link URL} after substitution.
082     */
083    public URL getUrl(Artifact artifact) {
084        URL url = getUrl();
085
086        if (url != null && artifact != null) {
087            TreeMap<String,String> map = new MapImpl(artifact);
088            StringSubstitutor substitutor = new StringSubstitutor((StringLookup) map, "{", "}", '\\');
089
090            substitutor.setEnableSubstitutionInVariables(true);
091
092            String string = url.toString();
093
094            try {
095                url = new URL(substitutor.replace(string));
096            } catch (MalformedURLException exception) {
097                throw new IllegalArgumentException(exception);
098            } catch (NoSuchElementException exception) {
099                throw new IllegalArgumentException(String.format("No value defined for '%s' in '%s'",
100                                                                 exception.getMessage(), string));
101            }
102        }
103
104        return url;
105    }
106
107    private class MapImpl extends TreeMap<String,String> implements StringLookup {
108        private static final long serialVersionUID = -1285467833760198210L;
109
110        public MapImpl(Artifact artifact) {
111            super(String.CASE_INSENSITIVE_ORDER);
112
113            put("groupId", artifact.getGroupId());
114            put("artifactId", artifact.getArtifactId());
115            put("version", artifact.getBaseVersion());
116
117            put("g", get("groupId"));
118            put("a", get("artifactId"));
119            put("v", get("version"));
120
121            String version = get("version");
122
123            for (String key : asList("major", "minor", "micro", "patch")) {
124                Matcher matcher = NUMBER.matcher(version);
125
126                if (matcher.lookingAt()) {
127                    put(key, matcher.group());
128                    version = version.substring(matcher.group().length());
129                } else {
130                    break;
131                }
132
133                matcher = DOT.matcher(version);
134
135                if (matcher.lookingAt()) {
136                    version = version.substring(matcher.group().length());
137                    continue;
138                } else {
139                    break;
140                }
141            }
142        }
143
144        @Override
145        public String lookup(String key) {
146            String value = get(key);
147
148            if (value == null) {
149                String prefix = key.toLowerCase();
150                Set<String> set =
151                    tailMap(key).entrySet().stream()
152                    .filter(t -> t.getKey().startsWith(prefix))
153                    .map(t -> t.getValue())
154                    .collect(toSet());
155
156                if (set.size() == 1) {
157                    value = set.iterator().next();
158                } else {
159                    throw new NoSuchElementException(key);
160                }
161            }
162
163            return value;
164        }
165    }
166}