Project

General

Profile

Download (6.18 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
* Copyright (C) 2007 EDIT
3
* European Distributed Institute of Taxonomy
4
* http://www.e-taxonomy.eu
5
*
6
* The contents of this file are subject to the Mozilla Public License Version 1.1
7
* See LICENSE.TXT at the top of this package for the full license terms.
8
*/
9
package eu.etaxonomy.cdm.model.agent;
10

    
11
import java.util.regex.Matcher;
12
import java.util.regex.Pattern;
13

    
14
import org.apache.commons.lang3.StringUtils;
15

    
16
import eu.etaxonomy.cdm.common.UTF8;
17

    
18

    
19
/**
20
 * A class for handling ORCIDs (http://https://orcid.org/, https://support.orcid.org/hc/en-us/articles/360006897674).
21
 * It offers parsing and formatting functionality as well as validation.
22
 * A {@link ORCID} object can only be created by syntactic valid input.
23
 * It internally stores 1 strings (length=16).
24
 *
25
 *
26
 * @author a.mueller
27
 * @since 2019-11-08
28
 */
29
public final class ORCID implements java.io.Serializable{
30

    
31
    /**
32
     * Explicit serialVersionUID for interoperability.
33
     */
34
    private static final long serialVersionUID = 4304992020966546747L;
35

    
36
    public static final String ORCID_ORG = "orcid.org/";
37

    
38
	/**
39
	 * The default public ORCID proxy server
40
	 */
41
	public static final String HTTP_ORCID_ORG = "https://" + ORCID_ORG;
42

    
43
    private volatile transient int hashCode = -1;	// Zero ==> undefined
44

    
45

    
46
//********************************* VARIABLES *************************************/
47

    
48
	/**
49
	 * The base digits without prefix, checksum and hyphens("-")
50
	 */
51
	private String baseNumber;
52

    
53
	/**
54
	 * The checksum.
55
	 * @see #checkDigit()
56
	 */
57
	private String checkSum;
58

    
59
// ***************************** FACTORY METHODS ***************************************/
60

    
61
	public static ORCID fromString(String orcid) throws IllegalArgumentException{
62
		return new ORCID(orcid);
63
	}
64

    
65
// ******************************* CONSTRUCTOR ************************************/
66

    
67
	private ORCID(){} //empty constructor required for JAXB
68

    
69
//    /**
70
//     * Creates a doi by its registrantCode and its suffix
71
//     * @param registrantCode the registrant code, the is the part following the directoryIndicator "10."
72
//     * 	and preceding the first forward slash (followed by the suffix)
73
//     * @param suffix the suffix is the part of the DOI following the first forward slash. It is provided
74
//     * by the registrant
75
//     */
76
//    private ORCID(String registrantCode, String suffix) {
77
//    	//preliminary until prefix_registrantCode and suffix validation is implemented
78
//		this("10." + registrantCode + "/" + suffix);
79
//
80
//		//use only after validation of both parts
81
////		this.prefix_registrantCode = registrantCode;
82
////		this.suffix = suffix;
83
//	}
84

    
85
    private ORCID(String doiString) {
86
		parseOrcidString(doiString);
87
	}
88

    
89
//************************************ GETTER ***********************************/
90

    
91

    
92
	/**
93
	 * The pure number representation, including the checksum (this maybe 'X'
94
	 * so it's not only digits
95
	 * @return
96
	 */
97
	public String getDigitsOnly() {
98
		return baseNumber+checkSum;
99
	}
100

    
101

    
102
// ********************************************* PARSER *******************************/
103

    
104
	private static Pattern orcidPattern = Pattern.compile("^(\\d{4}("+UTF8.ANY_DASH_RE()+")?){3}\\d{3}[0-9Xx]?$");
105

    
106
	private void parseOrcidString(String orcid){
107
		if (StringUtils.isBlank(orcid)){
108
			throw new IllegalArgumentException("ORCID string must not be null or blank");
109
		}
110
		orcid = orcid.trim();
111
		if (orcid.startsWith("http:") ){
112
		    orcid = orcid.replaceFirst("http:", "https:").trim();  //https is the current display standard
113
		}
114

    
115
		//replace URI prefix
116
		if (orcid.startsWith(HTTP_ORCID_ORG)){
117
			orcid = orcid.replaceFirst(HTTP_ORCID_ORG, "");
118
		}else if (orcid.startsWith(ORCID_ORG)){
119
		    orcid = orcid.replaceFirst(ORCID_ORG, "");
120
        }
121

    
122
		//now we should have the pure orcid
123
		if (orcid.length() != 15 && orcid.length() != 16 && orcid.length() != 18 && orcid.length() != 19){
124
			//for persistence reason we currently restrict the length of DOIs to 1000
125
			throw new IllegalArgumentException("ORCIDs must have exactly 16 digits. 3 dashes ('-') may be included after each group of 4 digits.");
126
		}
127

    
128
		Matcher matcher = orcidPattern.matcher(orcid);
129
		if (!matcher.find()){
130
            throw new IllegalArgumentException("ORCID can not be parsed. It must have exactly 16 digits. 3 dashes ('-') may be included after each group of 4 digits.");
131
		}
132

    
133
		orcid = orcid.replaceAll(UTF8.ANY_DASH_RE(), "");
134

    
135
		if (orcid.length() == 16){
136
	        this.baseNumber = orcid.substring(0, 15);
137
		    this.checkSum = orcid.substring(15);
138
		    if (!checkDigit(baseNumber).equals(checkSum)){
139
		        throw new IllegalArgumentException("ORCID checksum not correct (last digit is checksum, see https://support.orcid.org/hc/en-us/articles/360006897674-Structure-of-the-ORCID-Identifier).");
140
		    }
141
		}else{
142
		    this.baseNumber = orcid;
143
		    this.checkSum = checkDigit(baseNumber);
144
		}
145
	}
146

    
147
	private String makeOrcid(){
148
		return baseNumber.substring(0,4) + "-" + baseNumber.substring(4,8) + "-"
149
		        + baseNumber.substring(8,12) + "-" + baseNumber.substring(12,15) + checkSum;
150
	}
151

    
152
	public String asURI(){
153
		return HTTP_ORCID_ORG + makeOrcid();
154
	}
155

    
156
	/**
157
     * Generates check digit as per ISO 7064 11,2.
158
     * (code from https://support.orcid.org/hc/en-us/articles/360006897674)
159
     */
160
	public String checkDigit(){
161
	    return checkDigit(baseNumber);
162
	}
163

    
164
	/**
165
	  * @see #checkDigit()
166
	  * @param baseDigits the base digits without the checkSum digit
167
	  */
168
	private static String checkDigit(String baseDigits) {
169
	    int total = 0;
170
	    for (int i = 0; i < baseDigits.length(); i++) {
171
	        int digit = Character.getNumericValue(baseDigits.charAt(i));
172
	        total = (total + digit) * 2;
173
	    }
174
	    int remainder = total % 11;
175
	    int result = (12 - remainder) % 11;
176
	    return result == 10 ? "X" : String.valueOf(result);
177
	}
178

    
179
//************************************************* toString/equals /hashCode *********************/
180

    
181
	@Override
182
	public int hashCode() {
183
		if (hashCode == -1) {
184
            hashCode = 31 * baseNumber.hashCode();
185
        }
186
        return hashCode;
187
	}
188

    
189
	@Override
190
	public boolean equals(Object obj) {
191
		if (obj instanceof ORCID){
192
			return this.baseNumber.equals(((ORCID)obj).baseNumber) &&
193
			        this.checkSum.equals(((ORCID)obj).checkSum);
194
		}
195
		return false;
196
	}
197

    
198
	@Override
199
	public String toString(){
200
		return asURI();
201
	}
202
}
(7-7/12)