View Javadoc
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 }