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