1 /*
2 * The contents of this file are subject to the terms of the Common Development and
3 * Distribution License (the License). You may not use this file except in compliance with the
4 * License.
5 *
6 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
7 * specific language governing permission and limitations under the License.
8 *
9 * When distributing Covered Software, include this CDDL Header Notice in each file and include
10 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
11 * Header, with the fields enclosed by brackets [] replaced by your own identifying
12 * information: "Portions Copyrighted [year] [name of copyright owner]".
13 *
14 * Copyright 2008 Sun Microsystems, Inc.
15 * Portions copyright 2011 ForgeRock AS
16 */
17
18 package org.forgerock.i18n.maven;
19
20 import java.util.regex.Matcher;
21 import java.util.regex.Pattern;
22
23 /**
24 * A representation of a message property key contained in a message property
25 * file. A key comprises of an upper-case name and an optional ordinal:
26 * <ul>
27 * <li>{@code NAME} is an upper-case string containing characters and the
28 * underscore character for describing the purpose of the message.</li>
29 * <li>{@code ORDINAL} is an integer that makes the message unique within the
30 * property file.</li>
31 * </ul>
32 * Message property keys have the following string representation and are parsed
33 * using the {@code valueOf(String)} method.
34 *
35 * <pre>
36 * NAME[_ORDINAL]
37 * </pre>
38 *
39 * If no ordinal is provided then it will default to {@code -1}. Ordinals should
40 * be used for messages may be used for support purposes since they provide a
41 * language independent means for identifying the message.
42 */
43 final class MessagePropertyKey implements Comparable<MessagePropertyKey> {
44 // Message property keys must contain an upper-case name, optionally
45 // containing underscore characters, followed by an optional ordinal.
46 private static final Pattern PATTERN = Pattern
47 .compile("^([A-Z][A-Z0-9_]*?)(_([0-9]+))?$");
48
49 /**
50 * Parses a message property key from a string value.
51 *
52 * @param keyString
53 * The property key string.
54 * @return The parsed message property key.
55 * @throws IllegalArgumentException
56 * If the message property string had an invalid syntax.
57 */
58 static MessagePropertyKey valueOf(final String keyString) {
59 final Matcher matcher = PATTERN.matcher(keyString);
60
61 if (!matcher.matches()) {
62 throw new IllegalArgumentException(
63 "Error processing "
64 + keyString
65 + ". The provided key string must be of the form NAME[_ORDINAL]");
66 }
67
68 if (matcher.group(3) == null) {
69 // No ordinal.
70 return new MessagePropertyKey(keyString, -1);
71 } else {
72 final String name = matcher.group(1);
73 final int ordinal = Integer.parseInt(matcher.group(3));
74 return new MessagePropertyKey(name, ordinal);
75 }
76 }
77
78 // The message name.
79 private final String name;
80
81 // The ordinal will be -1 if none was specified.
82 private final int ordinal;
83
84 // Pattern for searching the key in source files.
85 private final Pattern startRegex;
86 private final Pattern midRegex;
87 private final Pattern endRegex;
88
89 /**
90 * Creates a new message property key with the provided name and ordinal.
91 *
92 * @param name
93 * The name of the message key.
94 * @param ordinal
95 * The ordinal of the message key, or {@code -1} if none was
96 * provided.
97 */
98 private MessagePropertyKey(final String name, final int ordinal) {
99 this.name = name;
100 this.ordinal = ordinal;
101 this.startRegex = Pattern.compile(name + "[^A-Z0-9_].*");
102 this.midRegex = Pattern.compile(".*[^A-Z0-9_]" + name + "[^A-Z0-9_].*");
103 this.endRegex = Pattern.compile(".*[^A-Z0-9_]" + name);
104 }
105
106 /**
107 * {@inheritDoc}
108 */
109 public int compareTo(final MessagePropertyKey k) {
110 if (ordinal == k.ordinal) {
111 return name.compareTo(k.name);
112 } else {
113 return ordinal - k.ordinal;
114 }
115 }
116
117 /**
118 * {@inheritDoc}
119 */
120 @Override
121 public boolean equals(final Object obj) {
122 if (this == obj) {
123 return true;
124 } else if (obj instanceof MessagePropertyKey) {
125 final MessagePropertyKey k = (MessagePropertyKey) obj;
126 return this.compareTo(k) == 0;
127 } else {
128 return false;
129 }
130 }
131
132 /**
133 * {@inheritDoc}
134 */
135 @Override
136 public int hashCode() {
137 return 31 * name.hashCode() + ordinal;
138 }
139
140 /**
141 * Returns {@code true} if this message property key is present in the
142 * provided line of text.
143 *
144 * @param line
145 * The line of text.
146 * @return {@code true} if this message property key is present in the
147 * provided line of text.
148 */
149 boolean isPresent(final String line) {
150 if (!line.contains(name)) { // Avoid regex if possible.
151 return false;
152 } else if (midRegex.matcher(line).matches()) { // Most likely regex.
153 return true;
154 } else if (endRegex.matcher(line).matches()) {
155 return true;
156 } else if (startRegex.matcher(line).matches()) {
157 return true;
158 } else {
159 return line.equals(name); // Unlikely, but for completeness.
160 }
161 }
162
163 /**
164 * Returns the name of this message property key with the ordinal. This
165 * method is equivalent to calling:
166 *
167 * <pre>
168 * getName(true);
169 * </pre>
170 *
171 * @return The name of this message property key.
172 */
173 @Override
174 public String toString() {
175 return getName(true);
176 }
177
178 /**
179 * Returns the name of this message property key without the ordinal. This
180 * method is equivalent to calling:
181 *
182 * <pre>
183 * getName(false);
184 * </pre>
185 *
186 * @return The name of this message property key.
187 */
188 String getName() {
189 return getName(false);
190 }
191
192 /**
193 * Returns the name of this message property key optionally including the
194 * ordinal.
195 *
196 * @param includeOrdinal
197 * {@code true} if the ordinal should be appended to the key
198 * name.
199 * @return The name of this message property key.
200 */
201 String getName(final boolean includeOrdinal) {
202 if (!includeOrdinal || ordinal < 0) {
203 return name;
204 } else {
205 final StringBuilder builder = new StringBuilder(name);
206 builder.append("_");
207 builder.append(ordinal);
208 return builder.toString();
209 }
210 }
211
212 /**
213 * Returns the ordinal of this message property key.
214 *
215 * @return The ordinal of this message property key, or {@code -1} if it
216 * does not have an ordinal.
217 */
218 int getOrdinal() {
219 return ordinal;
220 }
221
222 }