1 19 20 package org.netbeans.modules.ruby.spi.project.support.rake; 21 22 import java.io.BufferedInputStream ; 23 import java.io.BufferedOutputStream ; 24 import java.io.ByteArrayInputStream ; 25 import java.io.ByteArrayOutputStream ; 26 import java.io.File ; 27 import java.io.IOException ; 28 import java.io.InputStream ; 29 import java.io.OutputStream ; 30 import java.net.URI ; 31 import java.net.URL ; 32 import java.util.HashMap ; 33 import java.util.Map ; 34 import java.util.Properties ; 35 import java.util.zip.CRC32 ; 36 import java.util.zip.Checksum ; 37 import javax.xml.transform.Transformer ; 38 import javax.xml.transform.TransformerException ; 39 import javax.xml.transform.TransformerFactory ; 40 import javax.xml.transform.stream.StreamResult ; 41 import javax.xml.transform.stream.StreamSource ; 42 import org.netbeans.api.project.ProjectManager; 43 import org.netbeans.modules.ruby.modules.project.rake.UserQuestionHandler; 44 import org.openide.ErrorManager; 45 import org.openide.filesystems.FileLock; 46 import org.openide.filesystems.FileObject; 47 import org.openide.filesystems.FileSystem; 48 import org.openide.filesystems.FileUtil; 49 import org.openide.util.Mutex; 50 import org.openide.util.MutexException; 51 import org.openide.util.NbBundle; 52 import org.openide.util.UserQuestionException; 53 import org.openide.util.Utilities; 54 55 60 public final class GeneratedFilesHelper { 61 62 66 public static final String BUILD_XML_PATH = "build.xml"; 68 72 public static final String BUILD_IMPL_XML_PATH = "nbproject/build-impl.xml"; 74 83 static final String GENFILES_PROPERTIES_PATH = "nbproject/genfiles.properties"; 85 86 private static final String KEY_SUFFIX_DATA_CRC = ".data.CRC32"; 88 89 private static final String KEY_SUFFIX_STYLESHEET_CRC = ".stylesheet.CRC32"; 91 92 private static final String KEY_SUFFIX_SCRIPT_CRC = ".script.CRC32"; 94 99 public static final int FLAG_MISSING = 2 << 0; 100 101 109 public static final int FLAG_MODIFIED = 2 << 1; 110 111 126 public static final int FLAG_OLD_PROJECT_XML = 2 << 2; 127 128 141 public static final int FLAG_OLD_STYLESHEET = 2 << 3; 142 143 155 public static final int FLAG_UNKNOWN = 2 << 4; 156 157 158 private final RakeProjectHelper h; 159 160 161 private final FileObject dir; 162 163 167 public GeneratedFilesHelper(RakeProjectHelper h) { 168 this.h = h; 169 dir = h.getProjectDirectory(); 170 } 171 172 183 public GeneratedFilesHelper(FileObject d) { 184 if (d == null || !d.isFolder() || d.getFileObject(RakeProjectHelper.PROJECT_XML_PATH) == null) { 185 throw new IllegalArgumentException ("Does not look like an Ant-based project: " + d); } 187 h = null; 188 dir = d; 189 } 190 191 213 public void generateBuildScriptFromStylesheet(final String path, final URL stylesheet) throws IOException , IllegalStateException { 214 if (path == null) { 215 throw new IllegalArgumentException ("Null path"); } 217 if (stylesheet == null) { 218 throw new IllegalArgumentException ("Null stylesheet"); } 220 try { 221 ProjectManager.mutex().writeAccess(new Mutex.ExceptionAction<Void >() { 222 public Void run() throws IOException { 223 if (h != null && h.isProjectXmlModified()) { 224 throw new IllegalStateException ("Cannot generate build scripts from a modified project"); } 226 dir.getFileSystem().runAtomicAction(new FileSystem.AtomicAction() { 229 public void run() throws IOException { 230 FileObject projectXml = dir.getFileObject(RakeProjectHelper.PROJECT_XML_PATH); 231 final FileObject buildScriptXml = FileUtil.createData(dir, path); 232 byte[] projectXmlData; 233 InputStream is = projectXml.getInputStream(); 234 try { 235 projectXmlData = load(is); 236 } finally { 237 is.close(); 238 } 239 byte[] stylesheetData; 240 is = stylesheet.openStream(); 241 try { 242 stylesheetData = load(is); 243 } finally { 244 is.close(); 245 } 246 final byte[] resultData; 247 TransformerFactory tf = TransformerFactory.newInstance(); 248 try { 249 StreamSource stylesheetSource = new StreamSource ( 250 new ByteArrayInputStream (stylesheetData), stylesheet.toExternalForm()); 251 Transformer t = tf.newTransformer(stylesheetSource); 252 File projectXmlF = FileUtil.toFile(projectXml); 253 assert projectXmlF != null; 254 StreamSource projectXmlSource = new StreamSource ( 255 new ByteArrayInputStream (projectXmlData), projectXmlF.toURI().toString()); 256 ByteArrayOutputStream result = new ByteArrayOutputStream (); 257 t.transform(projectXmlSource, new StreamResult (result)); 258 resultData = result.toByteArray(); 259 } catch (TransformerException e) { 260 throw (IOException )new IOException (e.toString()).initCause(e); 261 } 262 final EditableProperties p = new EditableProperties(true); 264 FileObject genfiles = dir.getFileObject(GENFILES_PROPERTIES_PATH); 265 if (genfiles != null && genfiles.isVirtual()) { 266 genfiles = null; 268 } 269 if (genfiles != null) { 270 is = genfiles.getInputStream(); 271 try { 272 p.load(is); 273 } finally { 274 is.close(); 275 } 276 } 277 p.setProperty(path + KEY_SUFFIX_DATA_CRC, 278 getCrc32(new ByteArrayInputStream (projectXmlData), projectXml)); 279 if (genfiles == null) { 280 p.setComment(path + KEY_SUFFIX_DATA_CRC, new String [] { 282 "# " + NbBundle.getMessage(GeneratedFilesHelper.class, "COMMENT_genfiles.properties_1"), "# " + NbBundle.getMessage(GeneratedFilesHelper.class, "COMMENT_genfiles.properties_2"), }, false); 285 } 286 p.setProperty(path + KEY_SUFFIX_STYLESHEET_CRC, 287 getCrc32(new ByteArrayInputStream (stylesheetData), stylesheet)); 288 p.setProperty(path + KEY_SUFFIX_SCRIPT_CRC, 289 computeCrc32(new ByteArrayInputStream (resultData))); 290 if (genfiles == null) { 291 genfiles = FileUtil.createData(dir, GENFILES_PROPERTIES_PATH); 292 } 293 final FileObject _genfiles = genfiles; 294 final FileSystem.AtomicAction body = new FileSystem.AtomicAction() { 298 public void run() throws IOException { 299 FileLock lock1 = buildScriptXml.lock(); 301 try { 302 FileLock lock2 = _genfiles.lock(); 303 try { 304 OutputStream os1 = new EolFilterOutputStream(buildScriptXml.getOutputStream(lock1)); 305 try { 306 OutputStream os2 = _genfiles.getOutputStream(lock2); 307 try { 308 os1.write(resultData); 309 p.store(os2); 310 } finally { 311 os2.close(); 312 } 313 } finally { 314 os1.close(); 315 } 316 } finally { 317 lock2.releaseLock(); 318 } 319 } finally { 320 lock1.releaseLock(); 321 } 322 } 323 }; 324 try { 325 body.run(); 326 } catch (UserQuestionException uqe) { 327 UserQuestionHandler.handle(uqe, new UserQuestionHandler.Callback() { 329 public void accepted() { 330 try { 332 body.run(); 333 } catch (UserQuestionException uqe2) { 334 UserQuestionHandler.handle(uqe2, new UserQuestionHandler.Callback() { 336 public void accepted() { 337 try { 338 body.run(); 339 } catch (IOException e) { 340 ErrorManager.getDefault().notify(e); 341 } 342 } 343 public void denied() {} 344 public void error(IOException e) { 345 ErrorManager.getDefault().notify(e); 346 } 347 }); 348 } catch (IOException e) { 349 ErrorManager.getDefault().notify(e); 351 } 352 } 353 public void denied() { 354 } 356 public void error(IOException e) { 357 ErrorManager.getDefault().notify(e); 358 } 360 }); 361 } 362 } 363 }); 364 return null; 365 } 366 }); 367 } catch (MutexException e) { 368 throw (IOException )e.getException(); 369 } 370 } 371 372 375 private static byte[] load(InputStream is) throws IOException { 376 int size = Math.max(1024, is.available()); ByteArrayOutputStream baos = new ByteArrayOutputStream (size); 378 byte[] buf = new byte[size]; 379 int read; 380 while ((read = is.read(buf)) != -1) { 381 baos.write(buf, 0, read); 382 } 383 return baos.toByteArray(); 384 } 385 386 422 public int getBuildScriptState(final String path, final URL stylesheet) throws IllegalStateException { 423 try { 424 return ProjectManager.mutex().readAccess(new Mutex.ExceptionAction<Integer >() { 425 public Integer run() throws IOException { 426 if (h != null && h.isProjectXmlModified()) { 427 throw new IllegalStateException ("Cannot generate build scripts from a modified project"); } 429 FileObject script = dir.getFileObject(path); 430 if (script == null || script.isVirtual()) { 431 return FLAG_MISSING; 432 } 433 int flags = 0; 434 Properties p = new Properties (); 435 FileObject genfiles = dir.getFileObject(GENFILES_PROPERTIES_PATH); 436 if (genfiles == null || genfiles.isVirtual()) { 437 return FLAG_UNKNOWN | FLAG_MODIFIED | 440 FLAG_OLD_PROJECT_XML | FLAG_OLD_STYLESHEET; 441 } 442 InputStream is = new BufferedInputStream (genfiles.getInputStream()); 443 try { 444 p.load(is); 445 } finally { 446 is.close(); 447 } 448 FileObject projectXml = dir.getFileObject(RakeProjectHelper.PROJECT_XML_PATH); 449 if (projectXml != null && !projectXml.isVirtual()) { 450 String crc = getCrc32(projectXml); 451 if (!crc.equals(p.getProperty(path + KEY_SUFFIX_DATA_CRC))) { 452 flags |= FLAG_OLD_PROJECT_XML; 453 } 454 } else { 455 flags |= FLAG_OLD_PROJECT_XML; 457 } 458 String crc = getCrc32(stylesheet); 459 if (!crc.equals(p.getProperty(path + KEY_SUFFIX_STYLESHEET_CRC))) { 460 flags |= FLAG_OLD_STYLESHEET; 461 } 462 crc = getCrc32(script); 463 if (!crc.equals(p.getProperty(path + KEY_SUFFIX_SCRIPT_CRC))) { 464 flags |= FLAG_MODIFIED; 465 } 466 return flags; 467 } 468 }); 469 } catch (MutexException e) { 470 ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, (IOException )e.getException()); 471 return FLAG_UNKNOWN | FLAG_MODIFIED | FLAG_OLD_PROJECT_XML | FLAG_OLD_STYLESHEET; 472 } 473 } 474 475 479 static String computeCrc32(InputStream is) throws IOException { 480 Checksum crc = new CRC32 (); 481 int last = -1; 482 int curr; 483 while ((curr = is.read()) != -1) { 484 if (curr != '\n' && last == '\r') { 485 crc.update('\n'); 486 } 487 if (curr != '\r') { 488 crc.update(curr); 489 } 490 last = curr; 491 } 492 if (last == '\r') { 493 crc.update('\n'); 494 } 495 int val = (int)crc.getValue(); 496 String hex = Integer.toHexString(val); 497 while (hex.length() < 8) { 498 hex = "0" + hex; } 500 return hex; 501 } 502 503 505 private static final Map <URL ,String > crcCache = new HashMap <URL ,String >(); 506 private static final Map <URL ,Long > crcCacheTimestampsXorSizes = new HashMap <URL ,Long >(); 507 508 509 private static synchronized String findCachedCrc32(URL u, long footprint) { 510 String crc = crcCache.get(u); 511 if (crc != null) { 512 Long l = crcCacheTimestampsXorSizes.get(u); 513 assert l != null; 514 if (l == footprint) { 515 return crc; 517 } 518 } 519 return null; 521 } 522 523 524 private static synchronized void cacheCrc32(String crc, URL u, long footprint) { 525 crcCache.put(u, crc); 526 crcCacheTimestampsXorSizes.put(u, footprint); 527 } 528 529 530 private static String getCrc32(InputStream is, FileObject fo) throws IOException { 531 URL u = fo.getURL(); 532 fo.refresh(); long footprint = fo.lastModified().getTime() ^ fo.getSize(); 534 String crc = findCachedCrc32(u, footprint); 535 if (crc == null) { 536 crc = computeCrc32(is); 537 cacheCrc32(crc, u, footprint); 538 } 539 return crc; 540 } 541 542 543 private static long checkFootprint(URL u) { 544 URL nested = FileUtil.getArchiveFile(u); 545 if (nested != null) { 546 u = nested; 547 } 548 if (u.getProtocol().equals("file")) { File f = new File (URI.create(u.toExternalForm())); 550 return f.lastModified() ^ f.length(); 551 } else { 552 return 0L; 553 } 554 } 555 556 557 private static String getCrc32(InputStream is, URL u) throws IOException { 558 long footprint = checkFootprint(u); 559 String crc = null; 560 if (footprint != 0L) { 561 crc = findCachedCrc32(u, footprint); 562 } 563 if (crc == null) { 564 crc = computeCrc32(is); 565 if (footprint != 0L) { 566 cacheCrc32(crc, u, footprint); 567 } 568 } 569 return crc; 570 } 571 572 573 private static String getCrc32(FileObject fo) throws IOException { 574 URL u = fo.getURL(); 575 fo.refresh(); 576 long footprint = fo.lastModified().getTime() ^ fo.getSize(); 577 String crc = findCachedCrc32(u, footprint); 578 if (crc == null) { 579 InputStream is = fo.getInputStream(); 580 try { 581 crc = computeCrc32(new BufferedInputStream (is)); 582 cacheCrc32(crc, u, footprint); 583 } finally { 584 is.close(); 585 } 586 } 587 return crc; 588 } 589 590 591 private static String getCrc32(URL u) throws IOException { 592 long footprint = checkFootprint(u); 593 String crc = null; 594 if (footprint != 0L) { 595 crc = findCachedCrc32(u, footprint); 596 } 597 if (crc == null) { 598 InputStream is = u.openStream(); 599 try { 600 crc = computeCrc32(new BufferedInputStream (is)); 601 if (footprint != 0L) { 602 cacheCrc32(crc, u, footprint); 603 } 604 } finally { 605 is.close(); 606 } 607 } 608 return crc; 609 } 610 611 641 public boolean refreshBuildScript(final String path, final URL stylesheet, final boolean checkForProjectXmlModified) throws IOException , IllegalStateException { 642 try { 643 return ProjectManager.mutex().writeAccess(new Mutex.ExceptionAction<Boolean >() { 644 public Boolean run() throws IOException { 645 int flags = getBuildScriptState(path, stylesheet); 646 if (shouldGenerateBuildScript(flags, checkForProjectXmlModified)) { 647 generateBuildScriptFromStylesheet(path, stylesheet); 648 return true; 649 } else { 650 return false; 651 } 652 } 653 }); 654 } catch (MutexException e) { 655 throw (IOException )e.getException(); 656 } 657 } 658 659 private static boolean shouldGenerateBuildScript(int flags, boolean checkForProjectXmlModified) { 660 if ((flags & GeneratedFilesHelper.FLAG_MISSING) != 0) { 661 return true; 663 } 664 if ((flags & GeneratedFilesHelper.FLAG_MODIFIED) != 0) { 665 return false; 670 } 671 if (!checkForProjectXmlModified) { 672 return true; 674 } 675 return (flags & (GeneratedFilesHelper.FLAG_OLD_PROJECT_XML | 677 GeneratedFilesHelper.FLAG_OLD_STYLESHEET)) != 0; 678 } 679 680 private static class EolFilterOutputStream extends BufferedOutputStream { 683 684 private boolean isActive = Utilities.isWindows(); 685 private int last = -1; 686 687 public EolFilterOutputStream(OutputStream os) { 688 super(os, 4096); 689 } 690 691 public void write(byte[] b, int off, int len) throws IOException { 692 if (isActive) { 693 for (int i = off; i < off + len; i++) { 694 write(b[i]); 695 } 696 } 697 else { 698 super.write(b, off, len); 699 } 700 } 701 702 public void write(int b) throws IOException { 703 if (isActive) { 704 if (b == '\n' && last != '\r') { 705 super.write('\r'); 706 } 707 last = b; 708 } 709 super.write(b); 710 } 711 712 } 713 } 714 | Popular Tags |