001package org.apache.maven.tools.plugin.javadoc;
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 java.util.Objects;
023import java.util.Optional;
024import java.util.regex.Matcher;
025import java.util.regex.Pattern;
026
027import org.codehaus.plexus.util.StringUtils;
028
029/**
030 * Describes a code reference used in javadoc tags {@code see}, {@code link} and {@code linkplain}.
031 * The format of the reference given as string is {@code module/package.class#member label}.
032 * Members must be separated with a {@code #} to be detected. 
033 * Targets either module, package, class or field/method/constructor in class.
034 * This class does not know whether the second part part refers to a package, class or both,
035 * as they use the same alphabet and separators.
036 * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/javadoc/doc-comment-spec.html#link">link tag specification</a>
037 */
038public class JavadocReference
039{
040    private final Optional<String> moduleName;
041
042    private final Optional<String> packageNameClassName;
043
044    private final Optional<String> member; // optional, but may appear with both className and packageName being null
045
046    private final Optional<String> label;
047
048    /*
049     * Test at https://regex101.com/r/eDzWNx/1
050     * Captures several groups: module name (1), package name and/or class name (2), member (3), label (4)
051     */
052    private static final Pattern REFERENCE_VALUE_PATTERN =
053        Pattern.compile( "^\\s*(?:(.+)/)??([^#\\s/]+)?(?:#([^\\s\\(]+(?:\\([^\\)]*\\))?))?(?: +([^\\s/]+)\\s*)?$" );
054
055    private static final int GROUP_INDEX_MODULE = 1;
056
057    private static final int GROUP_INDEX_PACKAGECLASS = 2;
058
059    private static final int GROUP_INDEX_MEMBER = 3;
060
061    private static final int GROUP_INDEX_LABEL = 4;
062
063    /**
064     * 
065     * @param reference the reference value to parse
066     * @return the created {@link JavadocReference}
067     * @throws IllegalArgumentException in case the reference has an invalid format
068     */
069    public static JavadocReference parse( String reference )
070    {
071        Matcher matcher = REFERENCE_VALUE_PATTERN.matcher( reference );
072        if ( !matcher.matches() )
073        {
074            throw new IllegalArgumentException( "Invalid format of javadoc reference: " + reference );
075        }
076        final Optional<String> moduleName = getOptionalGroup( matcher, GROUP_INDEX_MODULE );
077        final Optional<String> packageNameClassName = getOptionalGroup( matcher, GROUP_INDEX_PACKAGECLASS );
078        final Optional<String> member = getOptionalGroup( matcher, GROUP_INDEX_MEMBER );
079        final Optional<String> label = getOptionalGroup( matcher, GROUP_INDEX_LABEL );
080        return new JavadocReference( moduleName, packageNameClassName, member, label );
081    }
082
083    private static Optional<String> getOptionalGroup( Matcher matcher, int index )
084    {
085        String group = matcher.group( index );
086        if ( StringUtils.isNotEmpty( group ) )
087        {
088            return Optional.of( group );
089        }
090        else
091        {
092            return Optional.empty();
093        }
094    }
095
096    JavadocReference( Optional<String> moduleName, Optional<String> packageNameClassName,
097                      Optional<String> member, Optional<String> label )
098    {
099        this.moduleName = moduleName;
100        this.packageNameClassName = packageNameClassName;
101        this.member = member;
102        this.label = label;
103    }
104
105    public Optional<String> getModuleName()
106    {
107        return moduleName;
108    }
109
110    /**
111     * 
112     * @return a package name, a class name or a package name followed by a class name
113     */
114    public Optional<String> getPackageNameClassName()
115    {
116        return packageNameClassName;
117    }
118
119    public Optional<String> getMember()
120    {
121        return member;
122    }
123
124    public Optional<String> getLabel()
125    {
126        return label;
127    }
128
129    @Override
130    public String toString()
131    {
132        return "JavadocReference [moduleName=" + moduleName + ", packageNameClassName=" + packageNameClassName
133               + ", member=" + member + ", label=" + label + "]";
134    }
135
136    @Override
137    public int hashCode()
138    {
139        return Objects.hash( label, member, packageNameClassName, moduleName );
140    }
141
142    @Override
143    public boolean equals( Object obj )
144    {
145        if ( this == obj )
146        {
147            return true;
148        }
149        if ( obj == null )
150        {
151            return false;
152        }
153        if ( getClass() != obj.getClass() )
154        {
155            return false;
156        }
157        JavadocReference other = (JavadocReference) obj;
158        return Objects.equals( label, other.label ) && Objects.equals( member, other.member )
159            && Objects.equals( packageNameClassName, other.packageNameClassName )
160            && Objects.equals( moduleName, other.moduleName );
161    }
162
163}