MessageFile.java revision 2776:aa568700edd1
1/* 2 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24import java.io.*; 25import java.util.*; 26import java.util.regex.Matcher; 27import java.util.regex.Pattern; 28 29/** 30 * Class to facilitate manipulating compiler.properties. 31 */ 32class MessageFile { 33 static final Pattern emptyOrCommentPattern = Pattern.compile("( *#.*)?"); 34 static final Pattern typePattern = Pattern.compile("[-\\\\'A-Z\\.a-z ]+( \\([A-Za-z 0-9]+\\))?"); 35 static final Pattern infoPattern = Pattern.compile(String.format("# ([0-9]+: %s, )*[0-9]+: %s", 36 typePattern.pattern(), typePattern.pattern())); 37 38 /** 39 * A line of text within the message file. 40 * The lines form a doubly linked list for simple navigation. 41 */ 42 class Line { 43 String text; 44 Line prev; 45 Line next; 46 47 Line(String text) { 48 this.text = text; 49 } 50 51 boolean isEmptyOrComment() { 52 return emptyOrCommentPattern.matcher(text).matches(); 53 } 54 55 boolean isInfo() { 56 return infoPattern.matcher(text).matches(); 57 } 58 59 boolean hasContinuation() { 60 return (next != null) && text.endsWith("\\"); 61 } 62 63 Line insertAfter(String text) { 64 Line l = new Line(text); 65 insertAfter(l); 66 return l; 67 } 68 69 void insertAfter(Line l) { 70 assert l.prev == null && l.next == null; 71 l.prev = this; 72 l.next = next; 73 if (next == null) 74 lastLine = l; 75 else 76 next.prev = l; 77 next = l; 78 } 79 80 Line insertBefore(String text) { 81 Line l = new Line(text); 82 insertBefore(l); 83 return l; 84 } 85 86 void insertBefore(Line l) { 87 assert l.prev == null && l.next == null; 88 l.prev = prev; 89 l.next = this; 90 if (prev == null) 91 firstLine = l; 92 else 93 prev.next = l; 94 prev = l; 95 } 96 97 void remove() { 98 if (prev == null) 99 firstLine = next; 100 else 101 prev.next = next; 102 if (next == null) 103 lastLine = prev; 104 else 105 next.prev = prev; 106 prev = null; 107 next = null; 108 } 109 } 110 111 /** 112 * A message within the message file. 113 * A message is a series of lines containing a "name=value" property, 114 * optionally preceded by a comment describing the use of placeholders 115 * such as {0}, {1}, etc within the property value. 116 */ 117 static final class Message { 118 final Line firstLine; 119 private Info info; 120 121 Message(Line l) { 122 firstLine = l; 123 } 124 125 boolean needInfo() { 126 Line l = firstLine; 127 while (true) { 128 if (l.text.matches(".*\\{[0-9]+\\}.*")) 129 return true; 130 if (!l.hasContinuation()) 131 return false; 132 l = l.next; 133 } 134 } 135 136 Set<Integer> getPlaceholders() { 137 Pattern p = Pattern.compile("\\{([0-9]+)\\}"); 138 Set<Integer> results = new TreeSet<Integer>(); 139 Line l = firstLine; 140 while (true) { 141 Matcher m = p.matcher(l.text); 142 while (m.find()) 143 results.add(Integer.parseInt(m.group(1))); 144 if (!l.hasContinuation()) 145 return results; 146 l = l.next; 147 } 148 } 149 150 /** 151 * Get the Info object for this message. It may be empty if there 152 * if no comment preceding the property specification. 153 */ 154 Info getInfo() { 155 if (info == null) { 156 Line l = firstLine.prev; 157 if (l != null && l.isInfo()) 158 info = new Info(l.text); 159 else 160 info = new Info(); 161 } 162 return info; 163 } 164 165 /** 166 * Set the Info for this message. 167 * If there was an info comment preceding the property specification, 168 * it will be updated; otherwise, one will be inserted. 169 */ 170 void setInfo(Info info) { 171 this.info = info; 172 Line l = firstLine.prev; 173 if (l != null && l.isInfo()) 174 l.text = info.toComment(); 175 else 176 firstLine.insertBefore(info.toComment()); 177 } 178 179 /** 180 * Get all the lines pertaining to this message. 181 */ 182 List<Line> getLines(boolean includeAllPrecedingComments) { 183 List<Line> lines = new ArrayList<Line>(); 184 Line l = firstLine; 185 if (includeAllPrecedingComments) { 186 // scan back to find end of prev message 187 while (l.prev != null && l.prev.isEmptyOrComment()) 188 l = l.prev; 189 // skip leading blank lines 190 while (l.text.isEmpty()) 191 l = l.next; 192 } else { 193 if (l.prev != null && l.prev.isInfo()) 194 l = l.prev; 195 } 196 197 // include any preceding lines 198 for ( ; l != firstLine; l = l.next) 199 lines.add(l); 200 201 // include message lines 202 for (l = firstLine; l != null && l.hasContinuation(); l = l.next) 203 lines.add(l); 204 lines.add(l); 205 206 // include trailing blank line if present 207 l = l.next; 208 if (l != null && l.text.isEmpty()) 209 lines.add(l); 210 211 return lines; 212 } 213 } 214 215 /** 216 * An object to represent the comment that may precede the property 217 * specification in a Message. 218 * The comment is modelled as a list of fields, where the fields correspond 219 * to the placeholder values (e.g. {0}, {1}, etc) within the message value. 220 */ 221 static final class Info { 222 /** 223 * An ordered set of descriptions for a placeholder value in a 224 * message. 225 */ 226 static class Field { 227 boolean unused; 228 Set<String> values; 229 boolean listOfAny = false; 230 boolean setOfAny = false; 231 Field(String s) { 232 s = s.substring(s.indexOf(": ") + 2); 233 values = new LinkedHashSet<String>(Arrays.asList(s.split(" or "))); 234 for (String v: values) { 235 if (v.startsWith("list of")) 236 listOfAny = true; 237 if (v.startsWith("set of")) 238 setOfAny = true; 239 } 240 } 241 242 /** 243 * Return true if this field logically contains all the values of 244 * another field. 245 */ 246 boolean contains(Field other) { 247 if (unused != other.unused) 248 return false; 249 250 for (String v: other.values) { 251 if (values.contains(v)) 252 continue; 253 if (v.equals("null") || v.equals("string")) 254 continue; 255 if (v.equals("list") && listOfAny) 256 continue; 257 if (v.equals("set") && setOfAny) 258 continue; 259 return false; 260 } 261 return true; 262 } 263 264 /** 265 * Merge the values of another field into this field. 266 */ 267 void merge(Field other) { 268 unused |= other.unused; 269 values.addAll(other.values); 270 271 // cleanup unnecessary entries 272 273 if (values.contains("null") && values.size() > 1) { 274 // "null" is superceded by anything else 275 values.remove("null"); 276 } 277 278 if (values.contains("string") && values.size() > 1) { 279 // "string" is superceded by anything else 280 values.remove("string"); 281 } 282 283 if (values.contains("list")) { 284 // list is superceded by "list of ..." 285 for (String s: values) { 286 if (s.startsWith("list of ")) { 287 values.remove("list"); 288 break; 289 } 290 } 291 } 292 293 if (values.contains("set")) { 294 // set is superceded by "set of ..." 295 for (String s: values) { 296 if (s.startsWith("set of ")) { 297 values.remove("set"); 298 break; 299 } 300 } 301 } 302 303 if (other.values.contains("unused")) { 304 values.clear(); 305 values.add("unused"); 306 } 307 } 308 309 void markUnused() { 310 values = new LinkedHashSet<String>(); 311 values.add("unused"); 312 listOfAny = false; 313 setOfAny = false; 314 } 315 316 @Override 317 public String toString() { 318 return values.toString(); 319 } 320 } 321 322 /** The fields of the Info object. */ 323 List<Field> fields = new ArrayList<Field>(); 324 325 Info() { } 326 327 Info(String text) throws IllegalArgumentException { 328 if (!text.startsWith("# ")) 329 throw new IllegalArgumentException(); 330 String[] segs = text.substring(2).split(", "); 331 fields = new ArrayList<Field>(); 332 for (String seg: segs) { 333 fields.add(new Field(seg)); 334 } 335 } 336 337 Info(Set<String> infos) throws IllegalArgumentException { 338 for (String s: infos) 339 merge(new Info(s)); 340 } 341 342 boolean isEmpty() { 343 return fields.isEmpty(); 344 } 345 346 boolean contains(Info other) { 347 if (other.isEmpty()) 348 return true; 349 350 if (fields.size() != other.fields.size()) 351 return false; 352 353 Iterator<Field> oIter = other.fields.iterator(); 354 for (Field values: fields) { 355 if (!values.contains(oIter.next())) 356 return false; 357 } 358 359 return true; 360 } 361 362 void merge(Info other) { 363 if (fields.isEmpty()) { 364 fields.addAll(other.fields); 365 return; 366 } 367 368 if (other.fields.size() != fields.size()) 369 throw new IllegalArgumentException(); 370 371 Iterator<Field> oIter = other.fields.iterator(); 372 for (Field d: fields) { 373 d.merge(oIter.next()); 374 } 375 } 376 377 void markUnused(Set<Integer> used) { 378 for (int i = 0; i < fields.size(); i++) { 379 if (!used.contains(i)) 380 fields.get(i).markUnused(); 381 } 382 } 383 384 @Override 385 public String toString() { 386 return fields.toString(); 387 } 388 389 String toComment() { 390 StringBuilder sb = new StringBuilder(); 391 sb.append("# "); 392 String sep = ""; 393 int i = 0; 394 for (Field f: fields) { 395 sb.append(sep); 396 sb.append(i++); 397 sb.append(": "); 398 sep = ""; 399 for (String s: f.values) { 400 sb.append(sep); 401 sb.append(s); 402 sep = " or "; 403 } 404 sep = ", "; 405 } 406 return sb.toString(); 407 } 408 } 409 410 Line firstLine; 411 Line lastLine; 412 Map<String, Message> messages = new TreeMap<String, Message>(); 413 414 MessageFile(File file) throws IOException { 415 Reader in = new FileReader(file); 416 try { 417 read(in); 418 } finally { 419 in.close(); 420 } 421 } 422 423 MessageFile(Reader in) throws IOException { 424 read(in); 425 } 426 427 final void read(Reader in) throws IOException { 428 BufferedReader br = (in instanceof BufferedReader) 429 ? (BufferedReader) in 430 : new BufferedReader(in); 431 String line; 432 while ((line = br.readLine()) != null) { 433 Line l; 434 if (firstLine == null) 435 l = firstLine = lastLine = new Line(line); 436 else 437 l = lastLine.insertAfter(line); 438 if (line.startsWith("compiler.")) { 439 int eq = line.indexOf("="); 440 if (eq > 0) 441 messages.put(line.substring(0, eq), new Message(l)); 442 } 443 } 444 } 445 446 void write(File file) throws IOException { 447 Writer out = new FileWriter(file); 448 try { 449 write(out); 450 } finally { 451 out.close(); 452 } 453 } 454 455 void write(Writer out) throws IOException { 456 BufferedWriter bw = (out instanceof BufferedWriter) 457 ? (BufferedWriter) out 458 : new BufferedWriter(out); 459 for (Line l = firstLine; l != null; l = l.next) { 460 bw.write(l.text); 461 bw.write("\n"); // always use Unix line endings 462 } 463 bw.flush(); 464 } 465} 466