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 }