summaryrefslogtreecommitdiffstats
path: root/third_party/jmake
diff options
context:
space:
mode:
authoragrieve <agrieve@chromium.org>2015-10-02 10:27:54 -0700
committerCommit bot <commit-bot@chromium.org>2015-10-02 17:28:49 +0000
commitfef292dbfd9b6cea6ae67ad1e939f8efc489b039 (patch)
tree095862f848d1cf412bca98bc145778e2943783b9 /third_party/jmake
parentb136e1257ec0512058dfee15126386821c231d61 (diff)
downloadchromium_src-fef292dbfd9b6cea6ae67ad1e939f8efc489b039.zip
chromium_src-fef292dbfd9b6cea6ae67ad1e939f8efc489b039.tar.gz
chromium_src-fef292dbfd9b6cea6ae67ad1e939f8efc489b039.tar.bz2
Fix javac --incremental by using jmake for dependency analysis
BUG=536817 Review URL: https://codereview.chromium.org/1373723003 Cr-Commit-Position: refs/heads/master@{#352065}
Diffstat (limited to 'third_party/jmake')
-rw-r--r--third_party/jmake/BUILD.gn31
-rw-r--r--third_party/jmake/LICENSE341
-rw-r--r--third_party/jmake/OWNERS2
-rw-r--r--third_party/jmake/README.chromium16
-rw-r--r--third_party/jmake/src/org/pantsbuild/jmake/Base64.java80
-rw-r--r--third_party/jmake/src/org/pantsbuild/jmake/BinaryFileReader.java96
-rw-r--r--third_party/jmake/src/org/pantsbuild/jmake/BinaryFileWriter.java99
-rw-r--r--third_party/jmake/src/org/pantsbuild/jmake/BinaryProjectDatabaseReader.java281
-rw-r--r--third_party/jmake/src/org/pantsbuild/jmake/BinaryProjectDatabaseWriter.java363
-rw-r--r--third_party/jmake/src/org/pantsbuild/jmake/ClassFileReader.java595
-rw-r--r--third_party/jmake/src/org/pantsbuild/jmake/ClassInfo.java746
-rw-r--r--third_party/jmake/src/org/pantsbuild/jmake/ClassPath.java448
-rw-r--r--third_party/jmake/src/org/pantsbuild/jmake/CompatibilityChecker.java610
-rw-r--r--third_party/jmake/src/org/pantsbuild/jmake/Main.java899
-rw-r--r--third_party/jmake/src/org/pantsbuild/jmake/PCDContainer.java64
-rw-r--r--third_party/jmake/src/org/pantsbuild/jmake/PCDEntry.java111
-rw-r--r--third_party/jmake/src/org/pantsbuild/jmake/PCDManager.java1603
-rw-r--r--third_party/jmake/src/org/pantsbuild/jmake/PrivateException.java28
-rw-r--r--third_party/jmake/src/org/pantsbuild/jmake/PublicExceptions.java169
-rw-r--r--third_party/jmake/src/org/pantsbuild/jmake/RefClassFinder.java697
-rw-r--r--third_party/jmake/src/org/pantsbuild/jmake/TextProjectDatabaseReader.java107
-rw-r--r--third_party/jmake/src/org/pantsbuild/jmake/TextProjectDatabaseWriter.java144
-rw-r--r--third_party/jmake/src/org/pantsbuild/jmake/Utils.java355
23 files changed, 7885 insertions, 0 deletions
diff --git a/third_party/jmake/BUILD.gn b/third_party/jmake/BUILD.gn
new file mode 100644
index 0000000..7b9b0a9
--- /dev/null
+++ b/third_party/jmake/BUILD.gn
@@ -0,0 +1,31 @@
+# Copyright 2015 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/android/rules.gni")
+
+java_binary("jmake") {
+ java_files = [
+ "src/org/pantsbuild/jmake/CompatibilityChecker.java",
+ "src/org/pantsbuild/jmake/BinaryFileWriter.java",
+ "src/org/pantsbuild/jmake/PCDEntry.java",
+ "src/org/pantsbuild/jmake/TextProjectDatabaseWriter.java",
+ "src/org/pantsbuild/jmake/Base64.java",
+ "src/org/pantsbuild/jmake/PCDContainer.java",
+ "src/org/pantsbuild/jmake/Main.java",
+ "src/org/pantsbuild/jmake/ClassFileReader.java",
+ "src/org/pantsbuild/jmake/ClassPath.java",
+ "src/org/pantsbuild/jmake/BinaryFileReader.java",
+ "src/org/pantsbuild/jmake/PrivateException.java",
+ "src/org/pantsbuild/jmake/ClassInfo.java",
+ "src/org/pantsbuild/jmake/BinaryProjectDatabaseWriter.java",
+ "src/org/pantsbuild/jmake/PCDManager.java",
+ "src/org/pantsbuild/jmake/TextProjectDatabaseReader.java",
+ "src/org/pantsbuild/jmake/RefClassFinder.java",
+ "src/org/pantsbuild/jmake/Utils.java",
+ "src/org/pantsbuild/jmake/PublicExceptions.java",
+ "src/org/pantsbuild/jmake/BinaryProjectDatabaseReader.java",
+ ]
+ main_class = "org.pantsbuild.jmake.Main"
+ enable_incremental_javac = false
+}
diff --git a/third_party/jmake/LICENSE b/third_party/jmake/LICENSE
new file mode 100644
index 0000000..2fa033e
--- /dev/null
+++ b/third_party/jmake/LICENSE
@@ -0,0 +1,341 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
+
diff --git a/third_party/jmake/OWNERS b/third_party/jmake/OWNERS
new file mode 100644
index 0000000..dbccb98
--- /dev/null
+++ b/third_party/jmake/OWNERS
@@ -0,0 +1,2 @@
+agrieve@chromium.org
+yfriedman@chromium.org
diff --git a/third_party/jmake/README.chromium b/third_party/jmake/README.chromium
new file mode 100644
index 0000000..0caab77
--- /dev/null
+++ b/third_party/jmake/README.chromium
@@ -0,0 +1,16 @@
+Name: JMake
+URL: https://github.com/pantsbuild/jmake
+Version: 0
+Revision: 7761ee3e1537ccc61820c0d30061eb09edaf1c93
+License: GPL 2.0
+Security Critical: No
+License Android Compatible: No
+
+Description:
+Formerly known as Javamake, jmake is a compiler wrapper that figures out the
+minimal set of .java files that need to be rebuilt given a set of .java files
+that have changed.
+
+Local Modifications:
+* Removed unneeded files
+* Added BUILD.gn
diff --git a/third_party/jmake/src/org/pantsbuild/jmake/Base64.java b/third_party/jmake/src/org/pantsbuild/jmake/Base64.java
new file mode 100644
index 0000000..af02ca6
--- /dev/null
+++ b/third_party/jmake/src/org/pantsbuild/jmake/Base64.java
@@ -0,0 +1,80 @@
+/* Copyright (c) 2002-2013 Sun Microsystems, Inc. All rights reserved
+ *
+ * This program is distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+package org.pantsbuild.jmake;
+
+import java.util.Arrays;
+
+
+/**
+ * JMake needs to run against old versions of Java, that may not have JAXB's
+ * javax.xml.bind.DatatypeConverter. And we don't want JMake to depend on third-party external libraries,
+ * especially not just for this. So we implement a lightweight Base64 converter here ourselves.
+
+ * Note that sun.misc.BASE64Encoder is not official API and can go away at any time. Plus it inserts
+ * line breaks into its emitted string, which is not what we want. So we can't use that either.
+ */
+
+public class Base64 {
+ // The easiest way to grok this code is to think of Base64 as the following chain of
+ // conversions (ignoring padding issues):
+ // 3 bytes -> 24 bits -> 4 6-bit nibbles -> 4 indexes from 0-63 -> 4 characters.
+ private static final char[] indexToDigit =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray();
+ private static final int[] digitToIndex = new int[128];
+ static {
+ assert(indexToDigit.length == 64);
+ Arrays.fill(digitToIndex, -1);
+ for (int i = 0; i < indexToDigit.length; i++) digitToIndex[(int)indexToDigit[i]] = i;
+ }
+
+ private Base64() {}
+
+ public static char[] encode(byte[] in) {
+ char[] ret = new char[(in.length + 2) / 3 * 4];
+ int p = 0;
+ int i = 0;
+ while (i < in.length) {
+ // Lowest 24 bits count.
+ int bits = (in[i++] & 0xff) << 16 | (i < in.length ? in[i++] & 0xff : 0) << 8 | (i < in.length ? in[i++] & 0xff : 0);
+ ret[p++] = indexToDigit[(bits & 0xfc0000) >> 18];
+ ret[p++] = indexToDigit[(bits & 0x3f000) >> 12];
+ ret[p++] = indexToDigit[(bits & 0xfc0) >> 6];
+ ret[p++] = indexToDigit[bits & 0x3f];
+ }
+ assert(p == ret.length);
+ int padding = (3 - in.length % 3) % 3;
+ for (int j = ret.length - padding; j < ret.length; j++) ret[j] = '=';
+ return ret;
+ }
+
+ public static byte[] decode(char[] in) {
+ if (in.length % 4 != 0) throw new IllegalArgumentException("Base64-encoded string must be of length that is a multiple of 4.");
+ int len = in.length;
+ while(len > 0 && in[len - 1] == '=') len--;
+ int padding = in.length - len;
+ byte[] ret = new byte[in.length / 4 * 3 - padding];
+ int i = 0;
+ int p = 0;
+ while (i < len) {
+ char c0 = in[i++];
+ char c1 = in[i++];
+ char c2 = i < len ? in[i++] : 'A';
+ char c3 = i < len ? in[i++] : 'A';
+ if (c0 > 127 || c1 > 127 || c2 > 127 || c3 > 127) throw new IllegalArgumentException("Invalid Base64 digit in: " + c0 + c1 + c2 + c3);
+ int n0 = digitToIndex[c0];
+ int n1 = digitToIndex[c1];
+ int n2 = digitToIndex[c2];
+ int n3 = digitToIndex[c3];
+ if (n0 < 0 || n1 < 0 || n2 < 0 || n3 < 0) throw new IllegalArgumentException("Invalid Base64 digit in: " + c0 + c1 + c2 + c3);
+ int bits = (n0 << 18) | (n1 << 12) | (n2 << 6) | n3;
+ ret[p++] = (byte)((bits & 0xff0000) >> 16);
+ if (p < ret.length) ret[p++] = (byte)((bits & 0xff00) >> 8);
+ if (p < ret.length) ret[p++] = (byte)(bits & 0xff);
+ }
+ return ret;
+ }
+}
diff --git a/third_party/jmake/src/org/pantsbuild/jmake/BinaryFileReader.java b/third_party/jmake/src/org/pantsbuild/jmake/BinaryFileReader.java
new file mode 100644
index 0000000..6e2cb4e
--- /dev/null
+++ b/third_party/jmake/src/org/pantsbuild/jmake/BinaryFileReader.java
@@ -0,0 +1,96 @@
+/* Copyright (c) 2002-2008 Sun Microsystems, Inc. All rights reserved
+ *
+ * This program is distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+package org.pantsbuild.jmake;
+
+import java.io.DataInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+/**
+ * Basic operations for reading a byte array representing a binary file.
+ *
+ * @author Misha Dmitriev
+ * 10 November 2001
+ */
+public class BinaryFileReader {
+
+ protected byte[] buf;
+ protected int curBufPos;
+ protected String fileFullPath; // Required only for nice error reports
+
+ protected void initBuf(byte[] buf, String fileFullPath) {
+ this.buf = buf;
+ curBufPos = 0;
+ this.fileFullPath = fileFullPath;
+ }
+
+ protected char nextChar() {
+ return (char) (((buf[curBufPos++] & 255) << 8) + (buf[curBufPos++] & 255));
+ }
+
+ protected char getChar(int bufPos) {
+ return (char) (((buf[bufPos] & 255) << 8) + (buf[bufPos+1] & 255));
+ }
+
+ protected int nextInt() {
+ return ((buf[curBufPos++] & 255) << 24) + ((buf[curBufPos++] & 255) << 16) +
+ ((buf[curBufPos++] & 255) << 8) + (buf[curBufPos++] & 255);
+ }
+
+ protected int getInt(int bufPos) {
+ return ((buf[bufPos] & 255) << 24) + ((buf[bufPos+1] & 255) << 16) +
+ ((buf[bufPos+2] & 255) << 8) + (buf[bufPos+3] & 255);
+ }
+
+ protected long nextLong() {
+ long res = getLong(curBufPos);
+ curBufPos += 8;
+ return res;
+ }
+
+ protected long getLong(int bufPos) {
+ DataInputStream bufin =
+ new DataInputStream(new ByteArrayInputStream(buf, bufPos, 8));
+ try {
+ return bufin.readLong();
+ } catch (IOException e) {
+ throw new PrivateException(e);
+ }
+ }
+
+ protected float nextFloat() {
+ float res = getFloat(curBufPos);
+ curBufPos += 4;
+ return res;
+ }
+
+ protected float getFloat(int bufPos) {
+ DataInputStream bufin =
+ new DataInputStream(new ByteArrayInputStream(buf, bufPos, 4));
+ try {
+ return bufin.readFloat();
+ } catch (IOException e) {
+ throw new PrivateException(e);
+ }
+ }
+
+ protected double nextDouble() {
+ double res = getDouble(curBufPos);
+ curBufPos += 8;
+ return res;
+ }
+
+ protected double getDouble(int bufPos) {
+ DataInputStream bufin =
+ new DataInputStream(new ByteArrayInputStream(buf, bufPos, 8));
+ try {
+ return bufin.readDouble();
+ } catch (IOException e) {
+ throw new PrivateException(e);
+ }
+ }
+}
diff --git a/third_party/jmake/src/org/pantsbuild/jmake/BinaryFileWriter.java b/third_party/jmake/src/org/pantsbuild/jmake/BinaryFileWriter.java
new file mode 100644
index 0000000..01de415
--- /dev/null
+++ b/third_party/jmake/src/org/pantsbuild/jmake/BinaryFileWriter.java
@@ -0,0 +1,99 @@
+/* Copyright (c) 2002-2008 Sun Microsystems, Inc. All rights reserved
+ *
+ * This program is distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+package org.pantsbuild.jmake;
+
+/**
+ * Basic operations for writing to a byte array representing a binary file.
+ *
+ * @author Misha Dmitriev
+ * 30 January 2002
+ */
+public class BinaryFileWriter {
+
+ protected byte[] buf;
+ protected int curBufSize, bufInc, curBufPos, threshold;
+ private boolean bufferIncreaseAllowed = true;
+
+ protected void initBuf(int initSize) {
+ buf = new byte[initSize];
+ curBufSize = initSize;
+ bufInc = initSize / 5;
+ curBufPos = 0;
+ threshold = curBufSize - bufInc;
+ }
+
+ protected void increaseBuf() {
+ if (!bufferIncreaseAllowed) {
+ return;
+ }
+ byte newBuf[] = new byte[curBufSize + bufInc];
+ System.arraycopy(buf, 0, newBuf, 0, curBufPos);
+ buf = newBuf;
+ curBufSize = buf.length;
+ threshold = curBufSize - bufInc;
+ }
+
+ // This should be called with false only when we are sure that we set the exact size of the buffer
+ // and there is no need to increase it.
+ protected void setBufferIncreaseMode(boolean increaseMode) {
+ bufferIncreaseAllowed = increaseMode;
+ }
+
+ public byte[] getBuffer() {
+ return buf;
+ }
+
+
+ protected void writeByte(byte b) {
+ if (curBufPos > threshold) {
+ increaseBuf();
+ }
+ buf[curBufPos++] = b;
+ }
+
+ protected void writeChar(int ch) {
+ buf[curBufPos++] = (byte) ((ch >> 8) & 255);
+ buf[curBufPos++] = (byte) (ch & 255);
+ if (curBufPos > threshold) {
+ increaseBuf();
+ }
+ }
+
+ protected void writeInt(int i) {
+ buf[curBufPos++] = (byte) ((i >> 24) & 255);
+ buf[curBufPos++] = (byte) ((i >> 16) & 255);
+ buf[curBufPos++] = (byte) ((i >> 8) & 255);
+ buf[curBufPos++] = (byte) (i & 255);
+ if (curBufPos > threshold) {
+ increaseBuf();
+ }
+ }
+
+ protected void writeLong(long l) {
+ buf[curBufPos++] = (byte) ((l >> 56) & 255);
+ buf[curBufPos++] = (byte) ((l >> 48) & 255);
+ buf[curBufPos++] = (byte) ((l >> 40) & 255);
+ buf[curBufPos++] = (byte) ((l >> 32) & 255);
+ buf[curBufPos++] = (byte) ((l >> 24) & 255);
+ buf[curBufPos++] = (byte) ((l >> 16) & 255);
+ buf[curBufPos++] = (byte) ((l >> 8) & 255);
+ buf[curBufPos++] = (byte) (l & 255);
+ if (curBufPos > threshold) {
+ increaseBuf();
+ }
+ }
+
+ protected void writeFloat(float f) {
+ int i = Float.floatToIntBits(f);
+ writeInt(i);
+ }
+
+ protected void writeDouble(double d) {
+ long l = Double.doubleToLongBits(d);
+ writeLong(l);
+ }
+}
diff --git a/third_party/jmake/src/org/pantsbuild/jmake/BinaryProjectDatabaseReader.java b/third_party/jmake/src/org/pantsbuild/jmake/BinaryProjectDatabaseReader.java
new file mode 100644
index 0000000..e75e206
--- /dev/null
+++ b/third_party/jmake/src/org/pantsbuild/jmake/BinaryProjectDatabaseReader.java
@@ -0,0 +1,281 @@
+/* Copyright (c) 2002-2008 Sun Microsystems, Inc. All rights reserved
+ *
+ * This program is distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+package org.pantsbuild.jmake;
+
+import java.io.File;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * This class creates the internal representation of the project database from a byte array.
+ *
+ * @author Misha Dmitriev
+ * 2 March 2005
+ */
+public class BinaryProjectDatabaseReader extends BinaryFileReader {
+
+ private String stringTable[];
+ private Map<String,PCDEntry> pcd;
+ private int nOfEntries;
+ private int pdbFormat; // Currently supported values: 0x01030300 (jmake 1.3.3 and newer versions); 1 (all older versions)
+ // These are defined in Utils as PDB_FORMAT_CODE_LATEST and PDB_FORMAT_CODE_OLD
+
+ public Map<String,PCDEntry> readProjectDatabaseFromFile(File infile) {
+ byte buf[] = Utils.readFileIntoBuffer(infile);
+ return readProjectDatabase(buf, infile.toString());
+ }
+
+ public Map<String,PCDEntry> readProjectDatabase(byte[] pdbFile,
+ String pdbFileFullPath) {
+ initBuf(pdbFile, pdbFileFullPath);
+
+ readPreamble();
+ readStringTable();
+ pcd = new LinkedHashMap<String,PCDEntry>(nOfEntries * 4 / 3);
+
+ for (int i = 0; i < nOfEntries; i++) {
+ PCDEntry entry = readPCDEntry();
+ pcd.put(entry.className, entry);
+ }
+
+ stringTable = null; // Help the GC
+ return pcd;
+ }
+
+ private void readPreamble() {
+ if (buf.length < Utils.magicLength + 8) {
+ pdbCorruptedException("file too short");
+ }
+
+ for (int i = 0; i < Utils.magicLength; i++) {
+ if (buf[i] != Utils.MAGIC[i]) {
+ pdbCorruptedException("wrong project database header");
+ }
+ }
+
+ curBufPos += Utils.magicLength;
+ pdbFormat = nextInt();
+ if (pdbFormat != Utils.PDB_FORMAT_CODE_OLD && pdbFormat != Utils.PDB_FORMAT_CODE_LATEST) {
+ pdbCorruptedException("wrong version number");
+ }
+
+ int pdbSize = nextInt();
+ if (buf.length != Utils.MAGIC.length + 8 + pdbSize) {
+ pdbCorruptedException("file size does not match stored value");
+ }
+
+ nOfEntries = nextInt();
+ }
+
+ private void readStringTable() {
+ int size = nextInt();
+ stringTable = new String[size];
+ for (int i = 0; i < size; i++) {
+ stringTable[i] = nextString();
+ }
+ }
+
+ private PCDEntry readPCDEntry() {
+ String className = nextStringRef();
+ String javaFileFullPath = nextStringRef();
+ long classFileLastModified = nextLong();
+ long classFileFingerprint = nextLong();
+ ClassInfo classInfo = readClassInfo();
+
+ return new PCDEntry(className, javaFileFullPath, classFileLastModified, classFileFingerprint, classInfo);
+ }
+
+ private ClassInfo readClassInfo() {
+ int i, j, len;
+ ClassInfo res = new ClassInfo();
+
+ res.name = nextStringRef();
+ if (pdbFormat >= Utils.PDB_FORMAT_CODE_133) {
+ res.javacTargetRelease = nextInt();
+ } else {
+ res.javacTargetRelease = Utils.JAVAC_TARGET_RELEASE_OLDEST;
+ }
+
+ len = nextChar();
+ if (len > 0) {
+ String cpoolRefsToClasses[] = new String[len];
+ for (i = 0; i < len; i++) {
+ cpoolRefsToClasses[i] = nextStringRef();
+ }
+ res.cpoolRefsToClasses = cpoolRefsToClasses;
+ boolean isRefClassArray[] = new boolean[len];
+ for (i = 0; i < len; i++) {
+ isRefClassArray[i] = (buf[curBufPos++] != 0);
+ }
+ res.isRefClassArray = isRefClassArray;
+ }
+
+ len = nextChar();
+ if (len > 0) {
+ String cpoolRefsToFieldClasses[] = new String[len];
+ for (i = 0; i < len; i++) {
+ cpoolRefsToFieldClasses[i] = nextStringRef();
+ }
+ res.cpoolRefsToFieldClasses = cpoolRefsToFieldClasses;
+ String cpoolRefsToFieldNames[] = new String[len];
+ for (i = 0; i < len; i++) {
+ cpoolRefsToFieldNames[i] = nextStringRef();
+ }
+ res.cpoolRefsToFieldNames = cpoolRefsToFieldNames;
+ String cpoolRefsToFieldSignatures[] = new String[len];
+ for (i = 0; i < len; i++) {
+ cpoolRefsToFieldSignatures[i] = nextStringRef();
+ }
+ res.cpoolRefsToFieldSignatures = cpoolRefsToFieldSignatures;
+ }
+
+ len = nextChar();
+ if (len > 0) {
+ String cpoolRefsToMethodClasses[] = new String[len];
+ for (i = 0; i < len; i++) {
+ cpoolRefsToMethodClasses[i] = nextStringRef();
+ }
+ res.cpoolRefsToMethodClasses = cpoolRefsToMethodClasses;
+ String cpoolRefsToMethodNames[] = new String[len];
+ for (i = 0; i < len; i++) {
+ cpoolRefsToMethodNames[i] = nextStringRef();
+ }
+ res.cpoolRefsToMethodNames = cpoolRefsToMethodNames;
+ String cpoolRefsToMethodSignatures[] = new String[len];
+ for (i = 0; i < len; i++) {
+ cpoolRefsToMethodSignatures[i] = nextStringRef();
+ }
+ res.cpoolRefsToMethodSignatures = cpoolRefsToMethodSignatures;
+ }
+
+ res.accessFlags = nextChar();
+ res.isNonMemberNestedClass = (buf[curBufPos++] != 0);
+ if (!"java/lang/Object".equals(res.name)) {
+ res.superName = nextStringRef();
+ }
+
+ len = nextChar();
+ if (len > 0) {
+ String interfaces[] = new String[len];
+ for (i = 0; i < len; i++) {
+ interfaces[i] = nextStringRef();
+ }
+ res.interfaces = interfaces;
+ }
+
+ len = nextChar();
+ if (len > 0) {
+ String fieldNames[] = new String[len];
+ for (i = 0; i < len; i++) {
+ fieldNames[i] = nextStringRef();
+ }
+ res.fieldNames = fieldNames;
+ String fieldSignatures[] = new String[len];
+ for (i = 0; i < len; i++) {
+ fieldSignatures[i] = nextStringRef();
+ }
+ res.fieldSignatures = fieldSignatures;
+ char fieldAccessFlags[] = new char[len];
+ for (i = 0; i < len; i++) {
+ fieldAccessFlags[i] = nextChar();
+ }
+ res.fieldAccessFlags = fieldAccessFlags;
+ }
+
+ len = nextChar();
+ if (len > 0) {
+ Object primitiveConstantInitValues[] = new Object[len];
+ for (i = 0; i < len; i++) {
+ byte code = buf[curBufPos++];
+ switch (code) {
+ case 1:
+ primitiveConstantInitValues[i] = nextStringRef();
+ break;
+ case 2:
+ primitiveConstantInitValues[i] = Integer.valueOf(nextInt());
+ break;
+ case 3:
+ primitiveConstantInitValues[i] = Long.valueOf(nextLong());
+ break;
+ case 4:
+ primitiveConstantInitValues[i] = Float.valueOf(nextFloat());
+ break;
+ case 5:
+ primitiveConstantInitValues[i] =
+ Double.valueOf(nextDouble());
+ break;
+ default: // Nothing to do
+ }
+ }
+ res.primitiveConstantInitValues = primitiveConstantInitValues;
+ }
+
+ len = nextChar();
+ if (len > 0) {
+ String methodNames[] = new String[len];
+ for (i = 0; i < len; i++) {
+ methodNames[i] = nextStringRef();
+ }
+ res.methodNames = methodNames;
+ String methodSignatures[] = new String[len];
+ for (i = 0; i < len; i++) {
+ methodSignatures[i] = nextStringRef();
+ }
+ res.methodSignatures = methodSignatures;
+ char methodAccessFlags[] = new char[len];
+ for (i = 0; i < len; i++) {
+ methodAccessFlags[i] = nextChar();
+ }
+ res.methodAccessFlags = methodAccessFlags;
+ }
+
+ len = nextChar();
+ if (len > 0) {
+ String checkedExceptions[][] = new String[len][];
+ for (i = 0; i < len; i++) {
+ int len1 = nextChar();
+ if (len1 > 0) {
+ checkedExceptions[i] = new String[len1];
+ for (j = 0; j < len1; j++) {
+ checkedExceptions[i][j] = nextStringRef();
+ }
+ }
+ }
+ res.checkedExceptions = checkedExceptions;
+ }
+
+ len = nextChar();
+ if (len > 0) {
+ String nestedClasses[] = new String[len];
+ for (i = 0; i < len; i++) {
+ nestedClasses[i] = nextStringRef();
+ }
+ res.nestedClasses = nestedClasses;
+ }
+
+ res.initializeImmediateTransientFields();
+ return res;
+ }
+
+ private String nextString() {
+ int length = nextChar();
+ if (buf.length < curBufPos + length) {
+ pdbCorruptedException("data error");
+ }
+ String res = (new String(buf, curBufPos, length)).intern();
+ curBufPos += length;
+ return res;
+ }
+
+ private String nextStringRef() {
+ return stringTable[nextInt()];
+ }
+
+ private void pdbCorruptedException(String message) {
+ throw new PrivateException(new PublicExceptions.PDBCorruptedException(message));
+ }
+}
diff --git a/third_party/jmake/src/org/pantsbuild/jmake/BinaryProjectDatabaseWriter.java b/third_party/jmake/src/org/pantsbuild/jmake/BinaryProjectDatabaseWriter.java
new file mode 100644
index 0000000..fafaa39
--- /dev/null
+++ b/third_party/jmake/src/org/pantsbuild/jmake/BinaryProjectDatabaseWriter.java
@@ -0,0 +1,363 @@
+/* Copyright (c) 2002-2008 Sun Microsystems, Inc. All rights reserved
+ *
+ * This program is distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+package org.pantsbuild.jmake;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * This class implements writing into a byte array representing a project database
+ *
+ * @author Misha Dmitriev
+ * 2 March 2005
+ */
+public class BinaryProjectDatabaseWriter extends BinaryFileWriter {
+
+ private Map<String, PCDEntry> pcd = null;
+ private int nOfEntries;
+ private byte[] stringBuf;
+ private int curStringBufPos, stringBufInc, curStringBufWatermark, stringCount;
+ private StringHashTable stringHashTable = null;
+
+ public void writeProjectDatabaseToFile(File outfile, Map<String, PCDEntry> pcd) {
+ try {
+ byte[] buf = new BinaryProjectDatabaseWriter().writeProjectDatabase(pcd);
+ FileOutputStream out = new FileOutputStream(outfile);
+ out.write(buf);
+ out.close();
+ } catch (IOException e) {
+ throw new PrivateException(e);
+ }
+ }
+
+ public byte[] writeProjectDatabase(Map<String, PCDEntry> pcd) {
+ this.pcd = pcd;
+ nOfEntries = pcd.size();
+
+ // So far the constant here is chosen rather arbitrarily
+ initBuf(nOfEntries * 1000);
+
+ stringBuf = new byte[nOfEntries * 300];
+ stringBufInc = stringBuf.length / 5;
+ curStringBufWatermark = stringBuf.length - 20;
+ stringHashTable = new StringHashTable(stringBuf.length / 8);
+
+ for (PCDEntry entry : pcd.values()) {
+ writePCDEntry(entry);
+ }
+
+ // Now we have the string buffer and the main buffer. Write the end result
+ byte[] mainBuf = buf;
+ int mainBufSize = curBufPos;
+ int preambleSize = Utils.MAGIC.length + 8;
+ int stringBufSize = curStringBufPos;
+ int pdbSize = stringBufSize + mainBufSize + 8; // 8 is for nOfEntries and string table size
+ initBuf(preambleSize + pdbSize);
+ setBufferIncreaseMode(false);
+
+ writePreamble(pdbSize);
+ writeStringTable(stringBufSize);
+ System.arraycopy(mainBuf, 0, buf, curBufPos, mainBufSize);
+ return buf;
+ }
+
+ private void writePreamble(int pdbSize) {
+ System.arraycopy(Utils.MAGIC, 0, buf, 0, Utils.MAGIC.length);
+ curBufPos += Utils.MAGIC.length;
+
+ writeInt(Utils.PDB_FORMAT_CODE_LATEST); // Version number
+ writeInt(pdbSize);
+ writeInt(pcd.size());
+ }
+
+ private void writeStringTable(int stringBufSize) {
+ writeInt(stringCount);
+ System.arraycopy(stringBuf, 0, buf, curBufPos, stringBufSize);
+ curBufPos += stringBufSize;
+ }
+
+ private void writePCDEntry(PCDEntry entry) {
+ writeStringRef(entry.className);
+ writeStringRef(entry.javaFileFullPath);
+ writeLong(entry.oldClassFileLastModified);
+ writeLong(entry.oldClassFileFingerprint);
+ writeClassInfo(entry.oldClassInfo);
+ }
+
+ private void writeClassInfo(ClassInfo ci) {
+ int i, j, len;
+
+ writeStringRef(ci.name);
+ writeInt(ci.javacTargetRelease);
+
+ len = ci.cpoolRefsToClasses != null ? ci.cpoolRefsToClasses.length : 0;
+ writeChar(len);
+ if (len > 0) {
+ String cpoolRefsToClasses[] = ci.cpoolRefsToClasses;
+ for (i = 0; i < len; i++) {
+ writeStringRef(cpoolRefsToClasses[i]);
+ }
+ boolean isRefClassArray[] = ci.isRefClassArray;
+ for (i = 0; i < len; i++) {
+ byte b = isRefClassArray[i] ? (byte) 1 : (byte) 0;
+ writeByte(b);
+ }
+ }
+
+ len = ci.cpoolRefsToFieldClasses != null ? ci.cpoolRefsToFieldClasses.length
+ : 0;
+ writeChar(len);
+ if (len > 0) {
+ String cpoolRefsToFieldClasses[] = ci.cpoolRefsToFieldClasses;
+ for (i = 0; i < len; i++) {
+ writeStringRef(cpoolRefsToFieldClasses[i]);
+ }
+ String cpoolRefsToFieldNames[] = ci.cpoolRefsToFieldNames;
+ for (i = 0; i < len; i++) {
+ writeStringRef(cpoolRefsToFieldNames[i]);
+ }
+ String cpoolRefsToFieldSignatures[] = ci.cpoolRefsToFieldSignatures;
+ for (i = 0; i < len; i++) {
+ writeStringRef(cpoolRefsToFieldSignatures[i]);
+ }
+ }
+
+ len = ci.cpoolRefsToMethodClasses != null ? ci.cpoolRefsToMethodClasses.length
+ : 0;
+ writeChar(len);
+ if (len > 0) {
+ String cpoolRefsToMethodClasses[] = ci.cpoolRefsToMethodClasses;
+ for (i = 0; i < len; i++) {
+ writeStringRef(cpoolRefsToMethodClasses[i]);
+ }
+ String cpoolRefsToMethodNames[] = ci.cpoolRefsToMethodNames;
+ for (i = 0; i < len; i++) {
+ writeStringRef(cpoolRefsToMethodNames[i]);
+ }
+ String cpoolRefsToMethodSignatures[] =
+ ci.cpoolRefsToMethodSignatures;
+ for (i = 0; i < len; i++) {
+ writeStringRef(cpoolRefsToMethodSignatures[i]);
+ }
+ }
+
+ writeChar(ci.accessFlags);
+ byte b = ci.isNonMemberNestedClass ? (byte) 1 : (byte) 0;
+ writeByte(b);
+ if (!"java/lang/Object".equals(ci.name)) {
+ writeStringRef(ci.superName);
+ }
+
+ len = ci.interfaces != null ? ci.interfaces.length : 0;
+ writeChar(len);
+ if (len > 0) {
+ String interfaces[] = ci.interfaces;
+ for (i = 0; i < len; i++) {
+ writeStringRef(interfaces[i]);
+ }
+ }
+
+ len = ci.fieldNames != null ? ci.fieldNames.length : 0;
+ writeChar(len);
+ if (len > 0) {
+ String fieldNames[] = ci.fieldNames;
+ for (i = 0; i < len; i++) {
+ writeStringRef(fieldNames[i]);
+ }
+ String fieldSignatures[] = ci.fieldSignatures;
+ for (i = 0; i < len; i++) {
+ writeStringRef(fieldSignatures[i]);
+ }
+ char fieldAccessFlags[] = ci.fieldAccessFlags;
+ for (i = 0; i < len; i++) {
+ writeChar(fieldAccessFlags[i]);
+ }
+ }
+
+ len = ci.primitiveConstantInitValues != null ? ci.primitiveConstantInitValues.length
+ : 0;
+ writeChar(len);
+ if (len > 0) {
+ Object primitiveConstantInitValues[] =
+ ci.primitiveConstantInitValues;
+ for (i = 0; i < len; i++) {
+ Object pc = primitiveConstantInitValues[i];
+ if (pc != null) {
+ if (pc instanceof String) {
+ writeByte((byte)1);
+ writeStringRef((String) pc);
+ } else if (pc instanceof Integer) {
+ writeByte((byte)2);
+ writeInt(((Integer) pc).intValue());
+ } else if (pc instanceof Long) {
+ writeByte((byte)3);
+ writeLong(((Long) pc).longValue());
+ } else if (pc instanceof Float) {
+ writeByte((byte)4);
+ writeFloat(((Float) pc).floatValue());
+ } else if (pc instanceof Double) {
+ writeByte((byte)5);
+ writeDouble(((Double) pc).doubleValue());
+ }
+ } else {
+ writeByte((byte)0);
+ }
+ }
+ }
+
+ len = ci.methodNames != null ? ci.methodNames.length : 0;
+ writeChar(len);
+ if (len > 0) {
+ String methodNames[] = ci.methodNames;
+ for (i = 0; i < len; i++) {
+ writeStringRef(methodNames[i]);
+ }
+ String methodSignatures[] = ci.methodSignatures;
+ for (i = 0; i < len; i++) {
+ writeStringRef(methodSignatures[i]);
+ }
+ char methodAccessFlags[] = ci.methodAccessFlags;
+ for (i = 0; i < len; i++) {
+ writeChar(methodAccessFlags[i]);
+ }
+ }
+
+ len = ci.checkedExceptions != null ? ci.checkedExceptions.length : 0;
+ writeChar(len);
+ if (len > 0) {
+ String checkedExceptions[][] = ci.checkedExceptions;
+ for (i = 0; i < len; i++) {
+ int lenl = checkedExceptions[i] != null ? checkedExceptions[i].length
+ : 0;
+ writeChar(lenl);
+ if (lenl > 0) {
+ for (j = 0; j < lenl; j++) {
+ writeStringRef(checkedExceptions[i][j]);
+ }
+ }
+ }
+ }
+
+ len = ci.nestedClasses != null ? ci.nestedClasses.length : 0;
+ writeChar(len);
+ if (len > 0) {
+ String nestedClasses[] = ci.nestedClasses;
+ for (i = 0; i < len; i++) {
+ writeStringRef(nestedClasses[i]);
+ }
+ }
+ }
+
+ private void writeString(String s) {
+ byte sb[] = s.getBytes();
+ int len = sb.length;
+ if (curStringBufPos + len > curStringBufWatermark) {
+ // May need to adapt stringBufInc
+ if (len >= stringBufInc) {
+ stringBufInc = (stringBufInc + len) * 2;
+ } else {
+ stringBufInc = (stringBufInc * 5) / 4; // Still increase a little - observations show that otherwise we usually get here 20 more times
+ }
+ byte newStringBuf[] = new byte[stringBuf.length + stringBufInc];
+ System.arraycopy(stringBuf, 0, newStringBuf, 0, curStringBufPos);
+ stringBuf = newStringBuf;
+ curStringBufWatermark = stringBuf.length - 20;
+ }
+ stringBuf[curStringBufPos++] = (byte) ((len >> 8) & 255);
+ stringBuf[curStringBufPos++] = (byte) (len & 255);
+ System.arraycopy(sb, 0, stringBuf, curStringBufPos, len);
+ curStringBufPos += len;
+ }
+
+ private void writeStringRef(String s) {
+ int stringRef = stringHashTable.get(s);
+ if (stringRef == -1) {
+ stringHashTable.add(s, stringCount);
+ stringRef = stringCount;
+ writeString(s);
+ stringCount++;
+ }
+ writeInt(stringRef);
+ }
+
+ /** Maps Strings to integer numbers (their positions in String table) */
+ static class StringHashTable {
+
+ String keys[];
+ int values[];
+ int size, nOfElements, watermark;
+
+ StringHashTable(int size) {
+ size = makeLikePrimeNumber(size);
+ this.size = size;
+ keys = new String[size];
+ values = new int[size];
+ nOfElements = 0;
+ watermark = size * 3 / 4;
+ }
+
+ final int get(String key) {
+ int pos = (key.hashCode() & 0x7FFFFFFF) % size;
+
+ while (keys[pos] != null && !keys[pos].equals(key)) {
+ pos = (pos + 3) % size; // Relies on the fact that size % 3 != 0
+ }
+ if (key.equals(keys[pos])) {
+ return values[pos];
+ } else {
+ return -1;
+ }
+ }
+
+ final void add(String key, int value) {
+ if (nOfElements > watermark) {
+ rehash();
+ }
+
+ int pos = (key.hashCode() & 0x7FFFFFFF) % size;
+ while (keys[pos] != null) {
+ pos = (pos + 3) % size; // Relies on the fact that size % 3 != 0
+ }
+ keys[pos] = key;
+ values[pos] = value;
+ nOfElements++;
+ }
+
+ private final void rehash() {
+ String oldKeys[] = keys;
+ int oldValues[] = values;
+ int oldSize = size;
+ size = makeLikePrimeNumber(size * 3 / 2);
+ keys = new String[size];
+ values = new int[size];
+ nOfElements = 0;
+ watermark = size * 3 / 4;
+
+ for (int i = 0; i < oldSize; i++) {
+ if (oldKeys[i] != null) {
+ add(oldKeys[i], oldValues[i]);
+ }
+ }
+ }
+
+ private final int makeLikePrimeNumber(int no) {
+ no = (no / 2) * 2 + 1; // Make it an odd number
+ // Find the nearest "approximately prime" number
+ boolean prime = false;
+ do {
+ no += 2;
+ prime =
+ (no % 3 != 0 && no % 5 != 0 && no % 7 != 0 && no % 11 != 0 &&
+ no % 13 != 0 && no % 17 != 0 && no % 19 != 0 && no % 23 != 0 &&
+ no % 29 != 0 && no % 31 != 0 && no % 37 != 0 && no % 41 != 0);
+ } while (!prime);
+ return no;
+ }
+ }
+}
diff --git a/third_party/jmake/src/org/pantsbuild/jmake/ClassFileReader.java b/third_party/jmake/src/org/pantsbuild/jmake/ClassFileReader.java
new file mode 100644
index 0000000..3ece22d
--- /dev/null
+++ b/third_party/jmake/src/org/pantsbuild/jmake/ClassFileReader.java
@@ -0,0 +1,595 @@
+/* Copyright (c) 2002-2008 Sun Microsystems, Inc. All rights reserved
+ *
+ * This program is distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+package org.pantsbuild.jmake;
+
+import java.lang.reflect.Modifier;
+
+
+/**
+ * This class implements reading a byte array representing a class file and converting it into ClassInfo.
+ *
+ * @author Misha Dmitriev
+ * 2 March 2005
+ */
+public class ClassFileReader extends BinaryFileReader {
+
+ public static final int JAVA_MAGIC = -889275714; // 0xCAFEBABE
+ public static final int JAVA_MINOR_VERSION = 0;
+ public static final int JAVA_MIN_MAJOR_VERSION = 45;
+ public static final int JAVA_MIN_MINOR_VERSION = 3;
+ public static final int DEFAULT_MAJOR_VERSION = 46;
+ public static final int DEFAULT_MINOR_VERSION = 0;
+ public static final int JDK14_MAJOR_VERSION = 48;
+ public static final int JDK15_MAJOR_VERSION = 49;
+ public static final int JDK16_MAJOR_VERSION = 50;
+ public static final int JDK17_MAJOR_VERSION = 51;
+ public static final int JDK18_MAJOR_VERSION = 52;
+ public static final int CONSTANT_Utf8 = 1;
+ public static final int CONSTANT_Unicode = 2;
+ public static final int CONSTANT_Integer = 3;
+ public static final int CONSTANT_Float = 4;
+ public static final int CONSTANT_Long = 5;
+ public static final int CONSTANT_Double = 6;
+ public static final int CONSTANT_Class = 7;
+ public static final int CONSTANT_String = 8;
+ public static final int CONSTANT_Fieldref = 9;
+ public static final int CONSTANT_Methodref = 10;
+ public static final int CONSTANT_InterfaceMethodref = 11;
+ public static final int CONSTANT_NameandType = 12;
+ public static final int CONSTANT_MethodHandle = 15;
+ public static final int CONSTANT_MethodType = 16;
+ public static final int CONSTANT_InvokeDynamic = 18;
+ private ClassInfo classInfo = null;
+ private int cpOffsets[];
+ private Object cpObjectCache[];
+ private byte cpTags[];
+
+ public void readClassFile(byte[] classFile, ClassInfo classInfo, String classFileFullPath, boolean readFullInfo) {
+ initBuf(classFile, classFileFullPath);
+ this.classInfo = classInfo;
+
+ readPreamble();
+ readConstantPool(readFullInfo);
+ readIntermediate();
+ if (readFullInfo) {
+ readFields();
+ readMethods();
+ readAttributes();
+ }
+ }
+
+ private int versionWord(int major, int minor) {
+ return major * 1000 + minor;
+ }
+
+ private void readPreamble() {
+ int magic = nextInt();
+ if (magic != JAVA_MAGIC) {
+ throw classFileParseException("Illegal start of class file");
+ }
+ int minorVersion = nextChar();
+ int majorVersion = nextChar();
+ if (majorVersion > JDK14_MAJOR_VERSION ||
+ versionWord(majorVersion, minorVersion) <
+ versionWord(JAVA_MIN_MAJOR_VERSION, JAVA_MIN_MINOR_VERSION) ) {
+ if (majorVersion == JDK18_MAJOR_VERSION) {
+ classInfo.javacTargetRelease = Utils.JAVAC_TARGET_RELEASE_18;
+ } else if (majorVersion == JDK17_MAJOR_VERSION) {
+ classInfo.javacTargetRelease = Utils.JAVAC_TARGET_RELEASE_17;
+ } else if (majorVersion == JDK16_MAJOR_VERSION) {
+ classInfo.javacTargetRelease = Utils.JAVAC_TARGET_RELEASE_16;
+ } else if (majorVersion == JDK15_MAJOR_VERSION) {
+ classInfo.javacTargetRelease = Utils.JAVAC_TARGET_RELEASE_15;
+ } else {
+ throw classFileParseException("Wrong version: " + majorVersion + "." + minorVersion);
+ }
+ } else {
+ classInfo.javacTargetRelease = Utils.JAVAC_TARGET_RELEASE_OLDEST;
+ }
+ }
+
+ private void readConstantPool(boolean readFullInfo) {
+ int classRefsNo = 0;
+ int fieldRefsNo = 0;
+ int methodRefsNo = 0;
+
+ cpOffsets = new int[nextChar()];
+ cpTags = new byte[cpOffsets.length];
+ int ofs, len, classIdx, nameAndTypeIdx, nameIdx, sigIdx, utf8Idx;
+ int i = 1;
+ while (i < cpOffsets.length) {
+ byte tag = buf[curBufPos++];
+ cpOffsets[i] = curBufPos;
+ cpTags[i] = tag;
+ i++;
+ switch (tag) {
+ case CONSTANT_Utf8:
+ len = nextChar();
+ curBufPos += len;
+ break;
+
+ case CONSTANT_Class:
+ classRefsNo++;
+ curBufPos += 2;
+ break;
+
+ case CONSTANT_String:
+ case CONSTANT_MethodType:
+ curBufPos += 2;
+ break;
+
+ case CONSTANT_Fieldref:
+ fieldRefsNo++;
+ curBufPos += 4;
+ break;
+
+ case CONSTANT_Methodref:
+ case CONSTANT_InterfaceMethodref:
+ methodRefsNo++;
+ curBufPos += 4;
+ break;
+
+ case CONSTANT_MethodHandle:
+ curBufPos += 3;
+ break;
+
+ case CONSTANT_NameandType:
+ case CONSTANT_Integer:
+ case CONSTANT_Float:
+ case CONSTANT_InvokeDynamic:
+ curBufPos += 4;
+ break;
+
+ case CONSTANT_Long:
+ case CONSTANT_Double:
+ curBufPos += 8;
+ i++;
+ break;
+
+ default:
+ throw classFileParseException("Bad constant pool tag: " + tag + " at " + Integer.toString(curBufPos - 1));
+ }
+ }
+
+ cpObjectCache = new Object[cpOffsets.length];
+ if (!readFullInfo) {
+ return;
+ }
+
+ classInfo.cpoolRefsToClasses = new String[classRefsNo];
+ classInfo.isRefClassArray = new boolean[classRefsNo];
+ classInfo.cpoolRefsToFieldClasses = new String[fieldRefsNo];
+ classInfo.cpoolRefsToFieldNames = new String[fieldRefsNo];
+ classInfo.cpoolRefsToFieldSignatures = new String[fieldRefsNo];
+ classInfo.cpoolRefsToMethodClasses = new String[methodRefsNo];
+ classInfo.cpoolRefsToMethodNames = new String[methodRefsNo];
+ classInfo.cpoolRefsToMethodSignatures = new String[methodRefsNo];
+
+ int curClassRef = 0;
+ int curFieldRef = 0;
+ int curMethodRef = 0;
+
+ for (i = 0; i < cpOffsets.length; i++) {
+ ofs = cpOffsets[i];
+ switch (cpTags[i]) {
+ case CONSTANT_Class:
+ utf8Idx = getChar(ofs);
+ classInfo.cpoolRefsToClasses[curClassRef++] =
+ classNameAtCPIndex(utf8Idx, classInfo.isRefClassArray, curClassRef - 1);
+ //System.out.println("Read cpool ref to class: " + classInfo.cpoolRefsToClasses[curClassRef-1]);
+ break;
+
+ case CONSTANT_Fieldref:
+ classIdx = getChar(ofs);
+ nameAndTypeIdx = getChar(ofs + 2);
+ if (cpTags[classIdx] != CONSTANT_Class || cpTags[nameAndTypeIdx] != CONSTANT_NameandType) {
+ badCPReference(ofs, i);
+ }
+ classInfo.cpoolRefsToFieldClasses[curFieldRef] =
+ classNameAtCPIndex(getChar(cpOffsets[classIdx]));
+
+ ofs = cpOffsets[nameAndTypeIdx];
+ nameIdx = getChar(ofs);
+ sigIdx = getChar(ofs + 2);
+ if (cpTags[nameIdx] != CONSTANT_Utf8 || cpTags[sigIdx] != CONSTANT_Utf8) {
+ badCPReference(ofs, i);
+ }
+ classInfo.cpoolRefsToFieldNames[curFieldRef] =
+ utf8AtCPIndex(nameIdx);
+ classInfo.cpoolRefsToFieldSignatures[curFieldRef] =
+ signatureAtCPIndex(sigIdx);
+ //System.out.println("Read cpool ref to field: " + classInfo.cpoolRefsToFieldNames[curFieldRef] + " " +
+ // classInfo.cpoolRefsToFieldSignatures[curFieldRef]);
+ curFieldRef++;
+ break;
+
+ case CONSTANT_Methodref:
+ case CONSTANT_InterfaceMethodref:
+ classIdx = getChar(ofs);
+ nameAndTypeIdx = getChar(ofs + 2);
+ if (cpTags[classIdx] != CONSTANT_Class || cpTags[nameAndTypeIdx] != CONSTANT_NameandType) {
+ badCPReference(ofs, i);
+ }
+ classInfo.cpoolRefsToMethodClasses[curMethodRef] =
+ classNameAtCPIndex(getChar(cpOffsets[classIdx]));
+
+ ofs = cpOffsets[nameAndTypeIdx];
+ nameIdx = getChar(ofs);
+ sigIdx = getChar(ofs + 2);
+ if (cpTags[nameIdx] != CONSTANT_Utf8 || cpTags[sigIdx] != CONSTANT_Utf8) {
+ badCPReference(ofs, i);
+ }
+ classInfo.cpoolRefsToMethodNames[curMethodRef] =
+ utf8AtCPIndex(nameIdx);
+ classInfo.cpoolRefsToMethodSignatures[curMethodRef] =
+ signatureAtCPIndex(sigIdx);
+ //System.out.println("Read cpool ref to method: " + classInfo.cpoolRefsToMethodNames[curMethodRef] + " " +
+ // classInfo.cpoolRefsToMethodSignatures[curMethodRef]);
+ curMethodRef++;
+ break;
+ }
+ }
+ }
+
+ private void readIntermediate() {
+ int i, classIdx, superClassIdx;
+
+ classInfo.accessFlags = nextChar();
+ classIdx = nextChar();
+ if (cpTags[classIdx] != CONSTANT_Class) {
+ throw classFileParseException("Bad reference to this class name");
+ }
+ classInfo.name = classNameAtCPIndex(getChar(cpOffsets[classIdx]));
+ superClassIdx = nextChar();
+ if (!"java/lang/Object".equals(classInfo.name)) {
+ if (cpTags[superClassIdx] != CONSTANT_Class) {
+ throw classFileParseException("Bad reference to super class name");
+ }
+ classInfo.superName =
+ classNameAtCPIndex(getChar(cpOffsets[superClassIdx]));
+ }
+
+ char intfCount = nextChar();
+ if (intfCount != 0) {
+ classInfo.interfaces = new String[intfCount];
+ for (i = 0; i < intfCount; i++) {
+ classIdx = nextChar();
+ if (cpTags[classIdx] != CONSTANT_Class) {
+ throw classFileParseException("Bad reference to an implemented interface");
+ }
+ classInfo.interfaces[i] =
+ classNameAtCPIndex(getChar(cpOffsets[classIdx]));
+ }
+ }
+ }
+
+ private void readFields() {
+ int i, j;
+
+ char definedFieldCount = nextChar();
+ if (definedFieldCount == 0) {
+ return;
+ }
+
+ String names[] = new String[definedFieldCount];
+ String signatures[] = new String[definedFieldCount];
+ char accessFlags[] = new char[definedFieldCount];
+
+ // We are not going to record information on private fields which have either primitive or non-project-class
+ // (typically core-class) types. Such fields cannot affect anything except their own class, so we don't need them.
+ int ri = 0;
+
+ for (i = 0; i < definedFieldCount; i++) {
+ char flags = nextChar();
+ String name = utf8AtCPIndex(nextChar());
+ String sig = signatureAtCPIndex(nextChar());
+
+ boolean recordField =
+ !(Modifier.isPrivate(flags) &&
+ (ClassInfo.isPrimitiveFieldSig(sig) || classInfo.isNonProjectClassTypeFieldSig(sig)));
+
+ int attrCount = nextChar();
+ for (j = 0; j < attrCount; j++) {
+ int attrNameIdx = nextChar();
+ int attrLen = nextInt();
+ if (recordField && utf8AtCPIndex(attrNameIdx).equals("ConstantValue") &&
+ Modifier.isFinal(flags)) {
+ if (classInfo.primitiveConstantInitValues == null) {
+ classInfo.primitiveConstantInitValues =
+ new Object[definedFieldCount];
+ }
+ int constValueIdx = nextChar();
+ switch (cpTags[constValueIdx]) {
+ case CONSTANT_String:
+ classInfo.primitiveConstantInitValues[ri] =
+ utf8AtCPIndex(getChar(cpOffsets[constValueIdx]));
+ break;
+
+ case CONSTANT_Integer:
+ classInfo.primitiveConstantInitValues[ri] =
+ Integer.valueOf(getInt(cpOffsets[constValueIdx]));
+ break;
+
+ case CONSTANT_Long:
+ classInfo.primitiveConstantInitValues[ri] =
+ Long.valueOf(getLong(cpOffsets[constValueIdx]));
+ break;
+
+ case CONSTANT_Float:
+ classInfo.primitiveConstantInitValues[ri] =
+ Float.valueOf(getFloat(cpOffsets[constValueIdx]));
+ break;
+
+ case CONSTANT_Double:
+ classInfo.primitiveConstantInitValues[ri] =
+ Double.valueOf(getDouble(cpOffsets[constValueIdx]));
+ break;
+
+ default:
+ badCPEntry(constValueIdx);
+ }
+
+ } else {
+ curBufPos += attrLen;
+ }
+ }
+
+ if (recordField) {
+ names[ri] = name;
+ signatures[ri] = sig;
+ accessFlags[ri] = flags;
+ ri++;
+ }
+ }
+
+ if (ri == definedFieldCount) {
+ classInfo.fieldNames = names;
+ classInfo.fieldSignatures = signatures;
+ classInfo.fieldAccessFlags = accessFlags;
+ } else if (ri > 0) {
+ classInfo.fieldNames = new String[ri];
+ classInfo.fieldSignatures = new String[ri];
+ classInfo.fieldAccessFlags = new char[ri];
+ System.arraycopy(names, 0, classInfo.fieldNames, 0, ri);
+ System.arraycopy(signatures, 0, classInfo.fieldSignatures, 0, ri);
+ System.arraycopy(accessFlags, 0, classInfo.fieldAccessFlags, 0, ri);
+ }
+ }
+
+ private void readMethods() {
+ int i, j;
+
+ char methodCount = nextChar();
+ if (methodCount == 0) {
+ return;
+ }
+
+ String names[] = new String[methodCount];
+ String signatures[] = new String[methodCount];
+ char accessFlags[] = new char[methodCount];
+
+ for (i = 0; i < methodCount; i++) {
+ accessFlags[i] = nextChar();
+ names[i] = utf8AtCPIndex(nextChar());
+ signatures[i] = signatureAtCPIndex(nextChar());
+
+ int attrCount = nextChar();
+ for (j = 0; j < attrCount; j++) {
+ int attrNameIdx = nextChar();
+ int attrLen = nextInt();
+ if (utf8AtCPIndex(attrNameIdx).equals("Exceptions")) {
+ if (classInfo.checkedExceptions == null) {
+ classInfo.checkedExceptions = new String[methodCount][];
+ }
+ int nExceptions = nextChar();
+ String exceptions[] = new String[nExceptions];
+ for (int k = 0; k < nExceptions; k++) {
+ int excClassIdx = nextChar();
+ if (cpTags[excClassIdx] != CONSTANT_Class) {
+ badCPEntry(excClassIdx);
+ }
+ exceptions[k] =
+ classNameAtCPIndex(getChar(cpOffsets[excClassIdx]));
+ }
+ classInfo.checkedExceptions[i] = exceptions;
+ } else {
+ curBufPos += attrLen;
+ }
+ }
+ }
+
+ classInfo.methodNames = names;
+ classInfo.methodSignatures = signatures;
+ classInfo.methodAccessFlags = accessFlags;
+ }
+
+ /**
+ * This method actually reads only the information related to the nested classes, and
+ * records only those of them which are first level nested classes of this class. The class
+ * may also reference other classes which are not package members through the same
+ * InnerClasses attribute - their names would be processed when their respective enclosing
+ * classes are read.
+ */
+ private void readAttributes() {
+ String nestedClassPrefix = classInfo.name + "$";
+
+ char attrCount = nextChar();
+
+ for (int i = 0; i < attrCount; i++) {
+ int attrNameIdx = nextChar();
+ int attrLen = nextInt();
+ if (utf8AtCPIndex(attrNameIdx).equals("InnerClasses")) {
+ int nOfClasses = nextChar();
+ String nestedClasses[] = new String[nOfClasses];
+ char nestedClassAccessFlags[] = new char[nOfClasses];
+ boolean nestedClassNonMember[] = new boolean[nOfClasses];
+ int curIdx = 0;
+ for (int j = 0; j < nOfClasses; j++) {
+ int innerClassInfoIdx = nextChar();
+ int outerClassInfoIdx = nextChar();
+ int innerClassNameIdx = nextChar();
+ char innerClassAccessFlags = nextChar();
+
+ // Even if a class is private or non-member (innerClassAccessFlags has private bit set or
+ // outerClassInfoIdx == 0), we still should take this class into account, since it may e.g. extend
+ // a public class/implement a public interface, which, in turn, may be changed incompatibly.
+
+ String nestedClassFullName = classNameAtCPIndex(getChar(cpOffsets[innerClassInfoIdx]));
+
+ // We are only interested the nested classes whose enclosing class is this one.
+ if (!nestedClassFullName.startsWith(nestedClassPrefix))
+ continue;
+
+ // We are only interested in the directly nested classes of this class.
+ String nestedClassNameSuffix = nestedClassFullName.substring(nestedClassPrefix.length());
+
+ if (innerClassNameIdx == 0) {
+ // Nested class is anonymous. Suffix must be all digits.
+ if (findFirstNonDigit(nestedClassNameSuffix) != -1)
+ continue;
+ } else {
+ // Nested class is named.
+ String nestedClassSimpleName = utf8AtCPIndex(innerClassNameIdx);
+ // The simple case is Outer$Inner.
+ if (!nestedClassNameSuffix.equals(nestedClassSimpleName)) {
+ // The more complicated case is a local class. In JDK 1.5+ These are named,
+ // e.g., Outer$1Inner. Pre-JDK 1.5 they are named e.g., Outer$1$Inner.
+ int p = findFirstNonDigit(nestedClassNameSuffix);
+ if (p == -1)
+ continue;
+ if (classInfo.javacTargetRelease == Utils.JAVAC_TARGET_RELEASE_OLDEST &&
+ nestedClassNameSuffix.charAt(p++) != '$')
+ continue;
+ if (!nestedClassNameSuffix.substring(p).equals(nestedClassSimpleName))
+ continue;
+ }
+ }
+
+ // The name has passed all checks, so register it.
+
+ nestedClasses[curIdx] = nestedClassFullName;
+ nestedClassAccessFlags[curIdx] = innerClassAccessFlags;
+ nestedClassNonMember[curIdx] = (outerClassInfoIdx == 0);
+ curIdx++;
+ }
+ if (curIdx == nOfClasses) {
+ classInfo.nestedClasses = nestedClasses;
+ classInfo.nestedClassAccessFlags = nestedClassAccessFlags;
+ classInfo.nestedClassNonMember = nestedClassNonMember;
+ } else if (curIdx > 0) {
+ // We found fewer nested classes for this class than we originally expected, but still more than 0.
+ // Create a new array to fit their number exactly.
+ classInfo.nestedClasses = new String[curIdx];
+ classInfo.nestedClassAccessFlags = new char[curIdx];
+ classInfo.nestedClassNonMember = new boolean[curIdx];
+ System.arraycopy(nestedClasses, 0, classInfo.nestedClasses, 0, curIdx);
+ System.arraycopy(nestedClassAccessFlags, 0, classInfo.nestedClassAccessFlags, 0, curIdx);
+ System.arraycopy(nestedClassNonMember, 0, classInfo.nestedClassNonMember, 0, curIdx);
+ }
+ } else {
+ curBufPos += attrLen;
+ }
+ }
+ }
+
+ private int findFirstNonDigit(String s) {
+ for (int i = 0; i < s.length(); i++) {
+ if (!Character.isDigit(s.charAt(i)))
+ return i;
+ }
+ return -1;
+ }
+
+ private String utf8AtCPIndex(int idx) {
+ if (cpTags[idx] != CONSTANT_Utf8) {
+ throw classFileParseException("Constant pool entry " + idx + " should be UTF8 constant");
+ }
+ if (cpObjectCache[idx] == null) {
+ int utf8Len = getChar(cpOffsets[idx]);
+ // String interning reduces the size of the disk database very significantly
+ // (by one-third in one observed case), and also speeds up database search.
+ cpObjectCache[idx] =
+ (new String(buf, cpOffsets[idx] + 2, utf8Len)).intern();
+ }
+ return (String) cpObjectCache[idx];
+ }
+
+ private String classNameAtCPIndex(int idx) {
+ return classNameAtCPIndex(idx, null, 0);
+ }
+
+ /**
+ * Read class name at the given CONSTANT_Utf8 constant pool index, and return it
+ * trimmed of the possible '[' and 'L' prefixes and the ';' suffix.
+ */
+ private String classNameAtCPIndex(int idx, boolean isRefClassArray[], int isArrayIdx) {
+ if (cpTags[idx] != CONSTANT_Utf8) {
+ throw classFileParseException("Constant pool entry " + idx + " should be UTF8 constant");
+ }
+ boolean isArray = false;
+ if (cpObjectCache[idx] == null) {
+ int utf8Len = getChar(cpOffsets[idx]);
+ int stPos = cpOffsets[idx] + 2;
+ int initStPos = stPos;
+ while (buf[stPos] == '[') {
+ stPos++;
+ }
+ if (stPos != initStPos) {
+ isArray = true;
+ if (buf[stPos] == 'L') {
+ stPos++;
+ utf8Len--; // To get rid of the terminating ';'
+ }
+ }
+ utf8Len = utf8Len - (stPos - initStPos);
+ cpObjectCache[idx] = (new String(buf, stPos, utf8Len)).intern();
+ if (isRefClassArray != null) {
+ isRefClassArray[isArrayIdx] = isArray;
+ }
+ }
+ return (String) cpObjectCache[idx];
+ }
+
+ // We replace all "Lclassname;" in signatures with "@classname#" to simplify signature parsing during reference checking
+ private String signatureAtCPIndex(int idx) {
+ if (cpTags[idx] != CONSTANT_Utf8) {
+ throw classFileParseException("Constant pool entry " + idx + " should be UTF8 constant");
+ }
+ if (cpObjectCache[idx] == null) {
+ int utf8Len = getChar(cpOffsets[idx]);
+ byte tmp[] = new byte[utf8Len];
+ System.arraycopy(buf, cpOffsets[idx] + 2, tmp, 0, utf8Len);
+ boolean inClassName = false;
+ for (int i = 0; i < utf8Len; i++) {
+ if (!inClassName) {
+ if (tmp[i] == 'L') {
+ tmp[i] = '@';
+ inClassName = true;
+ }
+ } else if (tmp[i] == ';') {
+ tmp[i] = '#';
+ inClassName = false;
+ }
+ }
+ cpObjectCache[idx] = (new String(tmp)).intern();
+ }
+ return (String) cpObjectCache[idx];
+ }
+
+ private void badCPReference(int ofs, int i) {
+ throw classFileParseException("Bad constant pool reference: " + ofs + " from entry " + i);
+ }
+
+ private void badCPEntry(int entryNo) {
+ throw classFileParseException("Constant pool entry " + entryNo + " : invalid type");
+ }
+
+ private PrivateException classFileParseException(String msg) {
+ return new PrivateException(new PublicExceptions.ClassFileParseException(
+ "Error reading class file " + fileFullPath + ":\n" + msg));
+ }
+}
diff --git a/third_party/jmake/src/org/pantsbuild/jmake/ClassInfo.java b/third_party/jmake/src/org/pantsbuild/jmake/ClassInfo.java
new file mode 100644
index 0000000..9bfcd5a
--- /dev/null
+++ b/third_party/jmake/src/org/pantsbuild/jmake/ClassInfo.java
@@ -0,0 +1,746 @@
+/* Copyright (c) 2002-2008 Sun Microsystems, Inc. All rights reserved
+ *
+ * This program is distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+package org.pantsbuild.jmake;
+
+import java.io.Serializable;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A reflection of a class, in the form that allows fast checks and information obtaining.
+ *
+ * @author Misha Dmitriev
+ * 5 April 2004
+ */
+@SuppressWarnings("serial")
+public class ClassInfo implements Serializable {
+
+ public static final int VER_OLD = 0; // Old version
+ public static final int VER_NEW = 1; // New version
+ public static final int NO_VERSIONS = 2; // Non-project class, no change tracking
+ private transient PCDManager pcdm;
+ transient int verCode; // Version code for this ClassInfo - one of the above.
+ String name = null;
+ transient String packageName; // Package name; restored when database is reloaded
+ int javacTargetRelease = Utils.JAVAC_TARGET_RELEASE_OLDEST; // Can have values from Utils.JAVAC_TARGET_RELEASE_xxx
+ String cpoolRefsToClasses[]; // Directly referenced class names trimmed of array and 'L' prefixes and ';' suffixes
+ boolean isRefClassArray[]; // Indicates if a directly referenced class is actually an array class
+ // In all signatures we replace the 'L' and ';' symbols that enclose non-primitive type names with '@' and '#' respectively,
+ // so that class names inside signatures can be located fast and unambiguously.
+ String cpoolRefsToFieldClasses[]; // Defining classes of referenced fields, trimmed of enclosing 'L' and ';' symbols
+ String cpoolRefsToFieldNames[]; // Names of referenced fields
+ String cpoolRefsToFieldSignatures[]; // Signatures of referenced fields
+ String cpoolRefsToMethodClasses[]; // Defining classes of referenced methods, trimmed of enclosing 'L' and ';' symbols
+ String cpoolRefsToMethodNames[]; // Names of referenced methods
+ String cpoolRefsToMethodSignatures[]; // Signatures of referenced methods
+ char accessFlags; // isInterface flag included
+ boolean isNonMemberNestedClass = false; // True if this is a non-member nested class
+ String superName;
+ String interfaces[];
+ String fieldNames[];
+ String fieldSignatures[];
+ char fieldAccessFlags[];
+ Object primitiveConstantInitValues[];
+ String methodNames[];
+ String methodSignatures[];
+ char methodAccessFlags[];
+ String checkedExceptions[][];
+ transient ClassInfo directSubclasses[]; // Direct subclasses. Created lazily and not preserved on disk.
+ transient String directlyEnclosingClass; // Directly enclosing class name; restored when database is reloaded
+ transient String topLevelEnclosingClass; // Top-level enclosing class name; restored when database is reloaded
+ String nestedClasses[]; // Names of all nested classes. Don't make transient - it's used to check
+ // if nested classes for this class were added/deleted in new version
+ transient char nestedClassAccessFlags[]; // No need to store this information permanently
+ transient boolean nestedClassNonMember[]; // Ditto
+
+ /** Creates new ClassInfo out of a class file. The last parameter is needed only to produce sensible error reports.*/
+ public ClassInfo(byte[] classFileBytes, int verCode, PCDManager pcdm, String classFileFullPath) {
+ this.pcdm = pcdm;
+ this.verCode = verCode;
+ pcdm.getClassFileReader().readClassFile(classFileBytes, this, classFileFullPath, true);
+ packageName = Utils.getPackageName(name);
+ directlyEnclosingClass =
+ Utils.getDirectlyEnclosingClass(name, this.javacTargetRelease);
+ topLevelEnclosingClass = Utils.getTopLevelEnclosingClass(name);
+ }
+
+ /**
+ * Create a "lightweight" ClassInfo, that contains just the class name, super name, interfaces, flags and verCode.
+ * Used for non-project classes, that don't change themselves, for which we are only interested in type hierarchy structure.
+ */
+ public ClassInfo(byte[] classFileBytes, PCDManager pcdm, String classFileFullPath) {
+ this.pcdm = pcdm;
+ this.verCode = NO_VERSIONS;
+ pcdm.getClassFileReader().readClassFile(classFileBytes, this, classFileFullPath, false);
+ packageName = Utils.getPackageName(name);
+ directlyEnclosingClass =
+ Utils.getDirectlyEnclosingClass(name, this.javacTargetRelease);
+ topLevelEnclosingClass = Utils.getTopLevelEnclosingClass(name);
+ }
+
+ /** Even more lightweight variant - created for a deleted non-project class, to enable minimum possible checks. */
+ public ClassInfo(String name, PCDManager pcdm) {
+ this.pcdm = pcdm;
+ this.verCode = NO_VERSIONS;
+ this.name = name;
+ packageName = Utils.getPackageName(name);
+ directlyEnclosingClass = Utils.getDirectlyEnclosingClass(name, 0);
+ topLevelEnclosingClass = Utils.getTopLevelEnclosingClass(name);
+ }
+
+ public ClassInfo() {
+ }
+
+ /** Initialize transient data that can be initialized immediately after this ClassInfo is read from the project database */
+ public void initializeImmediateTransientFields() {
+ verCode = VER_OLD;
+
+ packageName = Utils.getPackageName(name);
+
+ directlyEnclosingClass =
+ Utils.getDirectlyEnclosingClass(name, this.javacTargetRelease);
+ topLevelEnclosingClass = Utils.getTopLevelEnclosingClass(name);
+ }
+
+ /**
+ * Called to restore the pointer to the current PCDManager after this ClassInfo is brought back
+ * from the store.
+ */
+ public void restorePCDM(PCDManager pcdm) {
+ this.pcdm = pcdm;
+ }
+
+ public boolean isInterface() {
+ return Modifier.isInterface(accessFlags);
+ }
+
+ public boolean isAbstract() {
+ return Modifier.isAbstract(accessFlags);
+ }
+
+ public boolean isPublic() {
+ return Modifier.isPublic(accessFlags);
+ }
+
+ /**
+ * Returns the names of the superclasses of the given class (transitively), that belong
+ * to the same project, plus those of the superclasses that can be found on the class path
+ * supplied to jmake, and on the boot class path.
+ */
+ public List<String> getAllSuperclassNames() {
+ List<String> res = new ArrayList<String>();
+ String superName = this.superName;
+ while (superName != null && !"java/lang/Object".equals(superName)) {
+ res.add(superName);
+ ClassInfo classInfo = pcdm.getClassInfoForName(verCode, superName);
+ if (classInfo == null) { // Class not in project (or deleted?). Try to find it and further superclasses in non-project classes
+ ClassPath.getSuperclasses(superName, res, pcdm);
+ break;
+ }
+ superName = classInfo.superName;
+ }
+ return res;
+ }
+
+ /**
+ * Returns the set of names of the interfaces transitively implemented by the given
+ * class, that belong to the same project.
+ */
+ public Set<String> getAllImplementedIntfNames() {
+ Set<String> res = new LinkedHashSet<String>();
+ addImplementedInterfaceNames(false, res);
+ return res;
+ }
+
+ /** Add to the given set the names of direct/all interfaces implemented by the given class. */
+ private void addImplementedInterfaceNames(boolean directOnly,
+ Set<String> intfSet) {
+ if (interfaces != null) {
+ for (int i = 0; i < interfaces.length; i++) {
+ String superIntfName = interfaces[i];
+ intfSet.add(superIntfName);
+ if (directOnly) {
+ continue;
+ }
+ ClassInfo superIntfInfo =
+ pcdm.getClassInfoForName(verCode, superIntfName);
+ if (superIntfInfo == null) { // Class not in project
+ ClassPath.addAllImplementedInterfaceNames(superIntfName, intfSet, pcdm);
+ } else {
+ superIntfInfo.addImplementedInterfaceNames(false, intfSet);
+ }
+ }
+ }
+
+ if (directOnly || superName == null ||
+ "java/lang/Object".equals(superName)) {
+ return;
+ }
+ ClassInfo superInfo = pcdm.getClassInfoForName(verCode, superName);
+ if (superInfo == null) { // Class not in project
+ ClassPath.addAllImplementedInterfaceNames(superName, intfSet, pcdm);
+ } else {
+ superInfo.addImplementedInterfaceNames(false, intfSet);
+ }
+ }
+
+ /** Returns the array of all direct subclasses of this class (array of zero length if there are none). */
+ public ClassInfo[] getDirectSubclasses() {
+ if (directSubclasses != null) {
+ return directSubclasses;
+ }
+
+ List<ClassInfo> listRes = new ArrayList<ClassInfo>();
+
+ for (PCDEntry entry : pcdm.entries()) {
+ ClassInfo classInfo = pcdm.getClassInfoForPCDEntry(verCode, entry);
+ if (classInfo == null) {
+ continue; // New or deleted class, depending on verCode
+ }
+ if (classInfo.superName.equals(name)) {
+ listRes.add(classInfo);
+ }
+ }
+
+ directSubclasses = listRes.toArray(new ClassInfo[listRes.size()]);
+ return directSubclasses;
+ }
+
+ /** Check if the initial values for the given primitive constatnts in two classes are the same. */
+ public static boolean constFieldInitValuesEqual(ClassInfo oldClassInfo, int oldFieldNo,
+ ClassInfo newClassInfo, int newFieldNo) {
+ Object oldInitValue = oldClassInfo.primitiveConstantInitValues == null ? null
+ : oldClassInfo.primitiveConstantInitValues[oldFieldNo];
+ Object newInitValue = newClassInfo.primitiveConstantInitValues == null ? null
+ : newClassInfo.primitiveConstantInitValues[newFieldNo];
+ if (oldInitValue == newInitValue) {
+ return true;
+ }
+ if (oldInitValue == null || newInitValue == null) {
+ return false;
+ }
+
+ if (oldInitValue instanceof Integer) {
+ if (((Integer) oldInitValue).intValue() == ((Integer) newInitValue).intValue()) {
+ return true;
+ } else {
+ return false;
+ }
+ } else if (oldInitValue instanceof String) {
+ if ( ((String) oldInitValue).equals((String) newInitValue) ) {
+ return true;
+ } else {
+ return false;
+ }
+ } else if (oldInitValue instanceof Long) {
+ if (((Long) oldInitValue).longValue() == ((Long) newInitValue).longValue()) {
+ return true;
+ } else {
+ return false;
+ }
+ } else if (oldInitValue instanceof Float) {
+ if (((Float) oldInitValue).floatValue() == ((Float) newInitValue).floatValue()) {
+ return true;
+ } else {
+ return false;
+ }
+ } else if (oldInitValue instanceof Double) {
+ if (((Double) oldInitValue).doubleValue() == ((Double) newInitValue).doubleValue()) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public boolean implementsInterfaceDirectly(String intfName) {
+ if (interfaces == null) {
+ return false;
+ }
+ for (int i = 0; i < interfaces.length; i++) {
+ if (intfName.equals(interfaces[i])) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Check if this class implements interface I or any subinterface of I directly */
+ public boolean implementsIntfOrSubintfDirectly(String intfName) {
+ if (interfaces == null) {
+ return false;
+ }
+ for (int i = 0; i < interfaces.length; i++) {
+ if (intfName.equals(interfaces[i])) {
+ return true;
+ }
+ // An interface can have multiple superinterfaces, all of which are listed in its "interfaces" array
+ // (although in the .java source it "extends" them all).
+ ClassInfo superIntfInfo =
+ pcdm.getClassInfoForName(verCode, interfaces[i]);
+ if (superIntfInfo == null) {
+ continue; // Class not in project
+ }
+ if (superIntfInfo.implementsIntfOrSubintfDirectly(intfName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Class C implements interface I indirectly, if C or some superclass of C directly implements I
+ * or some subinterface of I.
+ */
+ public boolean implementsInterfaceDirectlyOrIndirectly(String intfName) {
+ if (interfaces == null) {
+ return false;
+ }
+
+ if (implementsIntfOrSubintfDirectly(intfName)) {
+ return true;
+ }
+
+ if (superName != null) {
+ ClassInfo superInfo = pcdm.getClassInfoForName(verCode, superName);
+ if (superInfo == null) {
+ return false; // Class not in project
+ }
+ return superInfo.implementsInterfaceDirectlyOrIndirectly(intfName);
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns true if this class declares a field with the same name and type as
+ * the field number fieldNo in class classInfo.
+ */
+ public boolean declaresField(ClassInfo classInfo, int fieldNo) {
+ if (fieldNames == null) {
+ return false;
+ }
+ String fieldName = classInfo.fieldNames[fieldNo];
+ String fieldSignature = classInfo.fieldSignatures[fieldNo];
+
+ for (int i = 0; i < fieldNames.length; i++) {
+ if (fieldName.equals(fieldNames[i]) &&
+ fieldSignature.equals(fieldSignatures[i])) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Returns true if this class declares a field with the given name, signature and access */
+ public boolean declaresField(String name, String signature, boolean isStatic) {
+ if (fieldNames == null) {
+ return false;
+ }
+ signature = ("@" + signature + "#").intern();
+ for (int i = 0; i < fieldNames.length; i++) {
+ if (name.equals(fieldNames[i]) &&
+ signature.equals(fieldSignatures[i]) &&
+ Modifier.isStatic(fieldAccessFlags[i]) == isStatic) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if this class declares a method with the same name and signature as
+ * the method number methodNo in class classInfo.
+ */
+ public boolean declaresMethod(ClassInfo classInfo, int methodNo) {
+ if (methodNames == null) {
+ return false;
+ }
+ String methodName = classInfo.methodNames[methodNo];
+ String methodSignature = classInfo.methodSignatures[methodNo];
+
+ for (int i = 0; i < methodNames.length; i++) {
+ if (methodName.equals(methodNames[i]) &&
+ methodSignature.equals(methodSignatures[i])) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * If this class declares a method with the same name and signature as the given method,
+ * return its position. Otherwise, return -1.
+ */
+ public int getDeclaredMethodPos(ClassInfo classInfo, int methodNo) {
+ if (methodNames == null) {
+ return -1;
+ }
+ String methodName = classInfo.methodNames[methodNo];
+ String methodSignature = classInfo.methodSignatures[methodNo];
+
+ for (int i = 0; i < methodNames.length; i++) {
+ if (methodName.equals(methodNames[i]) &&
+ methodSignature.equals(methodSignatures[i])) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Returns a nonnegative number (position in the method array) if this class declares a method with the
+ * name methodName, and -1 otherwise.
+ */
+ public int declaresSameNameMethod(String methodName) {
+ if (methodNames == null) {
+ return -1;
+ }
+ for (int j = 0; j < methodNames.length; j++) {
+ if (methodName.equals(methodNames[j])) {
+ return j;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Check if this class references the given class in different ways, depending on thorDegree parameter.
+ * thorDegree = 0: the given class (but not its array class) directly from the constantpool.
+ *
+ * thorDegree = 1: the given class or its array class directly from the constantpool, as a
+ * type of a data field, as a type in a method signature or a thrown exception, as a directly
+ * implemented interface or a direct superclass
+ *
+ * thorDegree = 2: the given class or its array class directly or indirectly from the
+ * constantpool, as a type of a data field, as a type in a method signature or a thrown exception,
+ * as a directly/indirectly implemented interface or a direct/indirect superclass.
+ *
+ * isRefTypeInterface indicates whether className is an interface.
+ */
+ public boolean referencesClass(String className, boolean isRefTypeInterface, int thorDegree) {
+ int i;
+
+ if (thorDegree == 0) {
+ if (cpoolRefsToClasses == null) {
+ return false;
+ }
+ for (i = 0; i < cpoolRefsToClasses.length; i++) {
+ if (!isRefClassArray[i] &&
+ className.equals(cpoolRefsToClasses[i])) {
+ return true;
+ }
+ }
+ } else {
+ if (isSubclassOf(className, (thorDegree == 1))) {
+ return true;
+ }
+ if (isRefTypeInterface) {
+ if (thorDegree == 1) {
+ if (implementsInterfaceDirectly(className)) {
+ return true;
+ }
+ } else {
+ // Check for indirectly implemented interfaces
+ if (implementsInterfaceDirectlyOrIndirectly(className)) {
+ return true;
+ }
+ }
+ }
+
+ if (cpoolRefsToClasses != null) {
+ for (i = 0; i < cpoolRefsToClasses.length; i++) {
+ if (className.equals(cpoolRefsToClasses[i])) {
+ return true;
+ }
+ }
+ }
+ if (thorDegree == 2) {
+ // Check for indirect references from the constantpool
+ if (cpoolRefsToFieldSignatures != null) {
+ for (i = 0; i < cpoolRefsToFieldSignatures.length; i++) {
+ if (signatureIncludesClassName(cpoolRefsToFieldSignatures[i], className)) {
+ return true;
+ }
+ }
+ }
+ if (cpoolRefsToMethodNames != null) {
+ for (i = 0; i < cpoolRefsToMethodSignatures.length; i++) {
+ if (signatureIncludesClassName(cpoolRefsToMethodSignatures[i], className)) {
+ return true;
+ }
+ }
+ }
+ }
+
+ if (fieldSignatures != null) {
+ for (i = 0; i < fieldSignatures.length; i++) {
+ if (signatureIncludesClassName(fieldSignatures[i], className)) {
+ return true;
+ }
+ }
+ }
+ if (methodSignatures != null) {
+ for (i = 0; i < methodSignatures.length; i++) {
+ if (signatureIncludesClassName(methodSignatures[i], className)) {
+ return true;
+ }
+ }
+ }
+ if (checkedExceptions != null) {
+ for (i = 0; i < checkedExceptions.length; i++) {
+ if (checkedExceptions[i] != null) {
+ String excArray[] = checkedExceptions[i];
+ for (int j = 0; j < excArray.length; j++) {
+ if (className.equals(excArray[j])) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private static boolean signatureIncludesClassName(String signature, String className) {
+ int stIndex = signature.indexOf(className);
+ if (stIndex == -1) {
+ return false;
+ }
+ return ((stIndex != 0 && signature.charAt(stIndex - 1) == '@' && signature.charAt(stIndex + className.length()) == '#') ||
+ (stIndex == 0 && signature.length() == className.length()));
+ }
+
+ public boolean isSubclassOf(String className, boolean directOnly) {
+ if (className.equals(superName)) {
+ return true;
+ }
+ if (directOnly) {
+ return false;
+ }
+ String superName = this.superName;
+ while (superName != null) {
+ if (className.equals(superName)) {
+ return true;
+ }
+ ClassInfo classInfo = pcdm.getClassInfoForName(verCode, superName);
+ if (classInfo == null) {
+ break; // Class not in project
+ }
+ superName = classInfo.superName;
+ }
+ return false;
+ }
+
+ /**
+ * Check if this class references field number fieldNo of class fieldDefClassInfo. Let us call
+ * this field C.f. Actual reference contained in the constant pool may be not to C.f itself,
+ * but to Csub.f, where Csub is some subclass of C such that neither Csub nor any other class
+ * located between C and Csub in the class hierarchy redeclares f. We look up both "real"
+ * references C.f and "fake" references such as Csub.f.
+ */
+ public boolean referencesField(ClassInfo fieldDefClassInfo, int fieldNo) {
+ if (cpoolRefsToFieldNames == null) {
+ return false;
+ }
+ String fieldDefClassName = fieldDefClassInfo.name;
+ String fieldName = fieldDefClassInfo.fieldNames[fieldNo];
+ String fieldSig = fieldDefClassInfo.fieldSignatures[fieldNo];
+ for (int i = 0; i < cpoolRefsToFieldNames.length; i++) {
+ if (fieldName.equals(cpoolRefsToFieldNames[i]) &&
+ fieldSig.equals(cpoolRefsToFieldSignatures[i]) ) {
+ if (fieldDefClassName.equals(cpoolRefsToFieldClasses[i]) ) {
+ return true; // "real" reference
+ } else { // Check if this is a "fake" reference that resolves to the above "real" reference
+ ClassInfo classInThisCpool =
+ pcdm.getClassInfoForName(verCode, cpoolRefsToFieldClasses[i]);
+ if (classInThisCpool == null) {
+ continue; // Class not in project
+ }
+ if (!classInThisCpool.isSubclassOf(fieldDefClassInfo.name, false)) {
+ continue;
+ }
+
+ // Ok, now check that this field is not actually redeclared in fieldDefClassInfo or
+ // somewhere in between it and classInThisCpool
+ boolean redeclared = false;
+ ClassInfo curClass = classInThisCpool;
+ do {
+ if (curClass.declaresField(fieldDefClassInfo, fieldNo)) {
+ redeclared = true;
+ break;
+ }
+ String superName = curClass.superName;
+ curClass = pcdm.getClassInfoForName(verCode, superName);
+ if (curClass == null) {
+ break;
+ }
+ } while (curClass != fieldDefClassInfo);
+ if (!redeclared) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check if this class references method number methodNo of class methodDefClassInfo. Let us
+ * call this method C.m. Actual reference contained in the constant pool may be not to C.m
+ * itself, but to Csub.m, where Csub is some subclass of C such that neither Csub nor any
+ * other class located between C and Csub in the class hierarchy redeclares m. We look up
+ * both "real" references C.m and "fake" references such as Csub.m.
+ */
+ public boolean referencesMethod(ClassInfo methodDefClassInfo, int methodNo) {
+ if (cpoolRefsToMethodNames == null) {
+ return false;
+ }
+ String methodDefClassName = methodDefClassInfo.name;
+ String methodName = methodDefClassInfo.methodNames[methodNo];
+ String methodSig = methodDefClassInfo.methodSignatures[methodNo];
+ for (int i = 0; i < cpoolRefsToMethodNames.length; i++) {
+ if (methodName.equals(cpoolRefsToMethodNames[i]) &&
+ methodSig.equals(cpoolRefsToMethodSignatures[i])) {
+ if (methodDefClassName.equals(cpoolRefsToMethodClasses[i])) {
+ return true; // "real" reference
+ } else { // Check if this is a "fake" reference that resolves to the above "real" reference
+ // Be careful - class in the cpool may be not a project class (e.g. a core class).
+ ClassInfo classInThisCpool =
+ pcdm.getClassInfoForName(verCode, cpoolRefsToMethodClasses[i]);
+ if (classInThisCpool == null) {
+ continue; // Class not in project
+ }
+ if (classInThisCpool.isSubclassOf(methodDefClassInfo.name, false)) {
+ // Ok, now check that this method is not actually redeclared in classInThisCpool (which is
+ // lower in the hierarchy) or somewhere in between it and classInThisCpool
+ boolean redeclared = false;
+ ClassInfo curClass = classInThisCpool;
+ do {
+ if (curClass.declaresMethod(methodDefClassInfo, methodNo)) {
+ redeclared = true;
+ break;
+ }
+ String superName = curClass.superName;
+ curClass =
+ pcdm.getClassInfoForName(verCode, superName);
+ if (curClass == null) {
+ break;
+ }
+ } while (curClass != methodDefClassInfo);
+ if (!redeclared) {
+ return true;
+ }
+ } else if (methodDefClassInfo.isInterface() && classInThisCpool.implementsIntfOrSubintfDirectly(methodDefClassName)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * If this class has a method that throws the given exception, return its index. Otherwise return -1.
+ * The search starts from method with index startMethodIdx.
+ */
+ public int hasMethodThrowingException(ClassInfo excClassInfo, int startMethodIdx) {
+ if (checkedExceptions == null) {
+ return -1;
+ }
+ if (startMethodIdx >= checkedExceptions.length) {
+ return -1;
+ }
+ String excName = excClassInfo.name;
+ for (int i = startMethodIdx; i < checkedExceptions.length; i++) {
+ if (checkedExceptions[i] == null) {
+ continue;
+ }
+ String[] exc = checkedExceptions[i];
+ for (int j = 0; j < exc.length; j++) {
+ if (exc[j].equals(excName)) {
+ return i;
+ }
+ }
+ }
+ return -1;
+ }
+
+ public static abstract class MethodHandler {
+
+ abstract void handleMethod(ClassInfo ci, int methodIdx);
+ }
+
+ /**
+ * Check this class and all its superclasses (if includeSuperclasses == true) and superinterfaces (if includeInterfaces == true)
+ * for a method with the given name. If such a method is found, call h.handleMethod(classInfo, methodIdx).
+ */
+ public void findExistingSameNameMethods(String methodName, boolean includeSuperclasses, boolean includeInterfaces, MethodHandler h) {
+ String className = name;
+ ClassInfo classInfo;
+ while (className != null) {
+ classInfo = pcdm.getClassInfoForName(verCode, className);
+ if (classInfo == null) {
+ break; // Class not in project
+ }
+ String mNames[] = classInfo.methodNames;
+ int mNamesLen = mNames != null ? mNames.length : 0;
+ for (int i = 0; i < mNamesLen; i++) {
+ if (methodName.equals(mNames[i])) {
+ h.handleMethod(classInfo, i);
+ }
+ }
+ if (includeInterfaces && classInfo.interfaces != null) {
+ String intfNames[] = classInfo.interfaces;
+ for (int i = 0; i < intfNames.length; i++) {
+ ClassInfo superIntfInfo =
+ pcdm.getClassInfoForName(verCode, intfNames[i]);
+ if (superIntfInfo == null) {
+ continue; // Class not in project
+ }
+ superIntfInfo.findExistingSameNameMethods(methodName, true, includeInterfaces, h);
+ }
+ }
+ if (includeSuperclasses) {
+ className = classInfo.superName;
+ } else {
+ return;
+ }
+ }
+ }
+
+ public static boolean isPrimitiveFieldSig(String fieldSig) {
+ return fieldSig.indexOf('@') == -1;
+ }
+
+ /**
+ * Check if the given signature is of a class type, and that class does not belong to the project.
+ * It used to be a check for just a core type name, but sometimes people use JDK sources as e.g. a test
+ * case - so better perform a universal (and entirely correct, unlike just a core type name) test here.
+ */
+ public boolean isNonProjectClassTypeFieldSig(String fieldSig) {
+ int stPos = fieldSig.indexOf('@');
+ if (stPos == -1) {
+ return false;
+ }
+ int endPos = fieldSig.indexOf('#');
+ String className = fieldSig.substring(stPos + 1, endPos);
+ return (!pcdm.isProjectClass(verCode, className));
+ }
+
+ /** For debugging. */
+ public String toString() {
+ return name + (verCode == VER_OLD ? " OLD" : " NEW");
+ }
+}
diff --git a/third_party/jmake/src/org/pantsbuild/jmake/ClassPath.java b/third_party/jmake/src/org/pantsbuild/jmake/ClassPath.java
new file mode 100644
index 0000000..e6e1aeb
--- /dev/null
+++ b/third_party/jmake/src/org/pantsbuild/jmake/ClassPath.java
@@ -0,0 +1,448 @@
+/* Copyright (c) 2002-2008 Sun Microsystems, Inc. All rights reserved
+ *
+ * This program is distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+package org.pantsbuild.jmake;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * An instance of this class represents a class path, on which binary classes can be looked up.
+ * It also provides several static methods to create and utilize several specific class paths used
+ * throughout jmake.
+ *
+ * @author Misha Dmitriev
+ * 12 October 2004
+ */
+public class ClassPath {
+
+ private PathEntry[] paths;
+ private static ClassPath projectClassPath; // Class path (currently it can contain only JARs) containing sourceless project classes.
+ // See also the comment to standardClassPath.
+ private static ClassPath standardClassPath; // Class path that the user specifies via the -classpath option. A sum of the
+ // standardClassPath, the projectClassPath, and the virtualPath is passed to the compiler. Each of these
+ // class paths are also used to look up non-project superclasses/superinterfaces of
+ // project classes.
+ private static ClassPath bootClassPath, extClassPath; // Class paths that by default are sun.boot.class.path and all JARs on
+ // java.ext.class.path, respectively. They are used to look up non-project
+ // superclasses/superinterfaces of project classes. Their values can be changed using
+ // setBootClassPath() and setExtDirs().
+ private static ClassPath virtualPath; // Class path that the user specifies via the -vpath option.
+ private static String compilerUserClassPath; // Class path to be passed to the compiler; equals to the sum of values of parameters of
+ // setClassPath() and setProjectClassPath() methods.
+ private static String standardClassPathStr, projectClassPathStr, bootClassPathStr, extDirsStr,
+ virtualPathStr;
+ private static Map<String,ClassInfo> classCache;
+
+
+ static {
+ resetOnFinish();
+ }
+
+ /**
+ * Needed since some environments, e.g. NetBeans, can keep jmake classes in memory
+ * permanently. Thus unchanged class paths from previous, possibly unrelated invocations
+ * of jmake, may interfere with the current settings.
+ */
+ public static void resetOnFinish() {
+ projectClassPath = standardClassPath = bootClassPath = extClassPath = virtualPath =
+ null;
+ compilerUserClassPath = null;
+ standardClassPathStr = projectClassPathStr = bootClassPathStr =
+ extDirsStr = virtualPathStr = null;
+ classCache = new LinkedHashMap<String,ClassInfo>();
+ }
+
+ public static void setClassPath(String value) throws PublicExceptions.InvalidCmdOptionException {
+ standardClassPathStr = value;
+ standardClassPath = new ClassPath(value, false);
+ }
+
+ public static void setProjectClassPath(String value) throws PublicExceptions.InvalidCmdOptionException {
+ projectClassPathStr = value;
+ projectClassPath = new ClassPath(value, true);
+ }
+
+ public static void setBootClassPath(String value) throws PublicExceptions.InvalidCmdOptionException {
+ bootClassPathStr = value;
+ bootClassPath = new ClassPath(value, false);
+ }
+
+ public static void setExtDirs(String value) throws PublicExceptions.InvalidCmdOptionException {
+ extDirsStr = value;
+ // Extension class path needs special handling, since it consists of directories, which contain .jars
+ // So we need to find all these .jars in all these dirs and add them to extClassPathElementList
+ List<String> extClassPathElements = new ArrayList<String>();
+ for (StringTokenizer tok =
+ new StringTokenizer(value, File.pathSeparator); tok.hasMoreTokens();) {
+ File extDir = new File(tok.nextToken());
+ String[] extJars = extDir.list(new FilenameFilter() {
+
+ public boolean accept(File dir, String name) {
+ name = name.toLowerCase(Locale.ENGLISH);
+ return name.endsWith(".zip") || name.endsWith(".jar");
+ }
+ });
+ if (extJars == null) {
+ continue;
+ }
+ for (int i = 0; i < extJars.length; i++) {
+ extClassPathElements.add(extDir + File.separator + extJars[i]);
+ }
+ }
+ extClassPath = new ClassPath(extClassPathElements, false);
+ }
+
+ public static void setVirtualPath(String value) throws PublicExceptions.InvalidCmdOptionException {
+ if (value == null) {
+ throw new PublicExceptions.InvalidCmdOptionException("null argument");
+ }
+ StringTokenizer st = new StringTokenizer(value, File.pathSeparator);
+ while (st.hasMoreElements()) {
+ String dir = st.nextToken();
+ if ( ! (new File(dir)).isDirectory()) {
+ throw new PublicExceptions.InvalidCmdOptionException("Virtual path must contain only directories." +
+ " Entry " + dir + " is not a directory.");
+ }
+ }
+ virtualPathStr = value;
+ virtualPath = new ClassPath(value, false);
+ }
+
+ public static void initializeAllClassPaths() {
+ // First set the compiler class path value
+ if (standardClassPathStr == null && projectClassPathStr == null) {
+ compilerUserClassPath = ".";
+ } else if (standardClassPathStr == null) {
+ compilerUserClassPath = projectClassPathStr;
+ } else if (projectClassPathStr == null) {
+ compilerUserClassPath = standardClassPathStr;
+ } else {
+ compilerUserClassPath =
+ standardClassPathStr + File.pathSeparator + projectClassPathStr;
+ }
+
+ if (virtualPathStr != null) {
+ compilerUserClassPath += File.pathSeparator + virtualPathStr;
+ }
+
+ if (standardClassPathStr == null) {
+ try {
+ String tmp = ".";
+ if (virtualPathStr != null) {
+ tmp += File.pathSeparator + virtualPathStr;
+ }
+ standardClassPath = new ClassPath(tmp, false);
+ } catch (PublicExceptions.InvalidCmdOptionException ex) { /* Should not happen */ }
+ }
+ if (projectClassPathStr == null) {
+ projectClassPath = new ClassPath();
+ }
+
+ // Create the core class path as a combination of sun.boot.class.path and java.ext.dirs contents
+ if (bootClassPathStr == null) {
+ try {
+ bootClassPath =
+ new ClassPath(System.getProperty("sun.boot.class.path"), false);
+ } catch (PublicExceptions.InvalidCmdOptionException ex) { /* Shouldn't happen */ }
+ // bootClassPathStr should remain null, so that nothing that the user didn't specify is passed to the compiler
+ }
+
+ if (extDirsStr == null) {
+ try {
+ setExtDirs(System.getProperty("java.ext.dirs"));
+ } catch (PublicExceptions.InvalidCmdOptionException ex) { /* Shouldn't happen */ }
+ // extDirsStr should remain null, so that nothing that the user didn't specify is passed to the compiler
+ extDirsStr = null;
+ }
+ }
+
+ /** Never returns null - if classpath wasn't set explicitly, returns "." */
+ public static String getCompilerUserClassPath() {
+ return compilerUserClassPath;
+ }
+
+ /** Will return null if boot class path wasn't explicitly specified */
+ public static String getCompilerBootClassPath() {
+ return bootClassPathStr;
+ }
+
+ /** Will return null if extdirs weren't explicitly specified */
+ public static String getCompilerExtDirs() {
+ return extDirsStr;
+ }
+
+ /** Will return null if virtualPath wasn't explicitly specified */
+ public static String getVirtualPath() {
+ return virtualPathStr;
+ }
+
+ /**
+ * For the given class return the list of all of its superclasses (excluding Object), that can be loaded from
+ * projectClassPath or standardClassPath, plus the first superclass that can be loaded from coreClassPath.
+ * The latter is an optimization based on the assumption that core classes never change, or rather the programmer
+ * will recompile everything when they switch to a new JDK version. The optimization prevents us from wasting time
+ * repeatedly loading the same sets of core classes.
+ */
+ public static void getSuperclasses(String className,
+ Collection<String> res, PCDManager pcdm) {
+ int iterNo = 0;
+ while (!"java/lang/Object".equals(className)) {
+ ClassInfo ci = getClassInfoForName(className, pcdm);
+ if (ci == null) {
+ return;
+ }
+ if (iterNo++ > 0) {
+ res.add(ci.name);
+ }
+ className = ci.superName;
+ }
+ }
+
+ /**
+ * Add to the given set the names of all interfaces implemented by the given class, that can be loaded from
+ * projectClassPath or standardClassPath, plus the first interface on each branch that can be loaded from
+ * coreClassPath. It's the same optimization as in getSuperclasses().
+ */
+ public static void addAllImplementedInterfaceNames(String className,
+ Set<String> intfSet, PCDManager pcdm) {
+ if ("java/lang/Object".equals(className)) {
+ return;
+ }
+ ClassInfo ci = getClassInfoForName(className, pcdm);
+ if (ci == null) {
+ return;
+ }
+ String[] interfaces = ci.interfaces;
+ if (interfaces != null) {
+ for (int i = 0; i < interfaces.length; i++) {
+ intfSet.add(interfaces[i]);
+ addAllImplementedInterfaceNames(interfaces[i], intfSet, pcdm);
+ }
+ }
+
+ String superName = ci.superName;
+ if (superName != null) {
+ addAllImplementedInterfaceNames(superName, intfSet, pcdm);
+ }
+ }
+
+ public static String[] getProjectJars() {
+ if (projectClassPath == null || projectClassPath.isEmpty()) {
+ return null;
+ }
+ PathEntry paths[] = projectClassPath.paths;
+ String[] ret = new String[paths.length];
+ for (int i = 0; i < paths.length; i++) {
+ ret[i] = paths[i].toString();
+ }
+ return ret;
+ }
+
+ public static ClassInfo getClassInfoForName(String className, PCDManager pcdm) {
+ ClassInfo info = classCache.get(className);
+ if (info != null) {
+ return info;
+ }
+
+ byte buf[] = bootClassPath.getBytesForClass(className);
+ if (buf == null) {
+ buf = extClassPath.getBytesForClass(className);
+ }
+ if (buf == null) {
+ buf = standardClassPath.getBytesForClass(className);
+ }
+ if (buf == null) {
+ buf = projectClassPath.getBytesForClass(className);
+ }
+ if (buf == null) {
+ return null;
+ }
+
+ info = new ClassInfo(buf, pcdm, className);
+ classCache.put(className, info);
+ return info;
+ }
+
+ /** Returns the class loader that would load classes from the given class path. */
+ public static ClassLoader getClassLoaderForPath(String classPath) throws Exception {
+ boolean isWindows = System.getProperty("os.name").startsWith("Win");
+ ClassPath cp = new ClassPath(classPath, false);
+ PathEntry[] paths = cp.paths;
+ URL[] urls = new URL[paths.length];
+ for (int i = 0; i < paths.length; i++) {
+ String dirOrJar = paths[i].toString();
+ if (!(dirOrJar.startsWith("file://") || dirOrJar.startsWith("http://"))) {
+ // On Windows, if I have path specified as "file://c:\...", (i.e. with the drive name) URLClassLoader works
+ // unbelievably slow. However, if an additional slash is added, like : "file:///c:\...", the speed becomes
+ // normal. To me it looks like a bug, but, anyway, I am taking measure here.
+ if (isWindows && dirOrJar.charAt(1) == ':') {
+ dirOrJar = "/" + dirOrJar;
+ }
+ dirOrJar = new File(dirOrJar).toURI().toString();
+ }
+ if (!(dirOrJar.endsWith(".jar") || dirOrJar.endsWith(".zip") || dirOrJar.endsWith(File.separator))) {
+ dirOrJar += File.separator; // So that URLClassLoader correctly handles it as a directory
+ }
+ urls[i] = new URL(dirOrJar);
+ }
+
+ return new URLClassLoader(urls);
+ //} catch (java.net.MalformedURLException e) {
+
+ //}
+ }
+
+
+ // ------------------------------------ Private implementation --------------------------------------------
+ private ClassPath() {
+ paths = new PathEntry[0];
+ }
+
+ private ClassPath(String classPath, boolean isJarOnly) throws PublicExceptions.InvalidCmdOptionException {
+ if (classPath == null) {
+ throw new PublicExceptions.InvalidCmdOptionException("null argument");
+ }
+ List<String> vec = new ArrayList<String>();
+
+ for (StringTokenizer tok =
+ new StringTokenizer(classPath, File.pathSeparator); tok.hasMoreTokens();) {
+ String path = tok.nextToken();
+ vec.add(path);
+ }
+ init(vec, isJarOnly);
+ }
+
+ private ClassPath(List<String> pathEntries, boolean isJarOnly) throws PublicExceptions.InvalidCmdOptionException {
+ init(pathEntries, isJarOnly);
+ }
+
+ private void init(List<String> pathEntries, boolean isJarOnly) throws PublicExceptions.InvalidCmdOptionException {
+ if (pathEntries == null) {
+ throw new PublicExceptions.InvalidCmdOptionException("null argument");
+ }
+ List<PathEntry> vec = new ArrayList<PathEntry>(pathEntries.size());
+ for (int i = 0; i < pathEntries.size(); i++) {
+ String path = pathEntries.get(i);
+ if (!path.equals("")) {
+ File file = new File(path);
+ try {
+ if (file.exists() && file.canRead()) {
+ if (file.isDirectory()) {
+ if (isJarOnly) {
+ throw new PublicExceptions.InvalidCmdOptionException("directories are not allowed on this class path: " + path);
+ }
+ vec.add(new Dir(file));
+ } else {
+ vec.add(new Zip(new ZipFile(file)));
+ }
+ } else if (isJarOnly) {
+ throw new IOException("file does not exist");
+ }
+ } catch (IOException e) {
+ if (isJarOnly) {
+ throw new PublicExceptions.InvalidCmdOptionException("error initializing class path component " + path + ": " + e.getMessage());
+ }
+ }
+ }
+ }
+
+ paths = new PathEntry[vec.size()];
+ vec.toArray(paths);
+ }
+
+ private boolean isEmpty() {
+ return paths.length == 0;
+ }
+
+ private byte[] getBytesForClass(String className) {
+ String fileName = className + ".class";
+ for (int i = 0; i < paths.length; i++) {
+ byte buf[] = paths[i].getBytesForClassFile(fileName);
+ if (buf != null) {
+ return buf;
+ }
+ }
+ return null;
+ }
+
+ public String toString() {
+ if (paths == null) {
+ return "NULL";
+ }
+ StringBuilder res = new StringBuilder();
+ for (int i = 0; i < paths.length; i++) {
+ res.append(paths[i].toString());
+ }
+ return res.toString();
+ }
+
+
+ // ------------------------------------ Private helper classes --------------------------------------------
+ private static abstract class PathEntry {
+
+ abstract byte[] getBytesForClassFile(String fileName);
+
+ public abstract String toString();
+ }
+
+ private static class Dir extends PathEntry {
+
+ private String dir;
+
+ Dir(File f) throws IOException {
+ dir = f.getCanonicalPath();
+ }
+
+ byte[] getBytesForClassFile(String fileName) {
+ File file = new File(dir + File.separatorChar + fileName);
+ if (file.exists()) {
+ return Utils.readFileIntoBuffer(file);
+ } else {
+ return null;
+ }
+ }
+
+ public String toString() {
+ return dir;
+ }
+ }
+
+ private static class Zip extends PathEntry {
+
+ private ZipFile zip;
+
+ Zip(ZipFile z) {
+ zip = z;
+ }
+
+ byte[] getBytesForClassFile(String fileName) {
+ ZipEntry entry = zip.getEntry(fileName);
+ if (entry != null) {
+ return Utils.readZipEntryIntoBuffer(zip, entry);
+ } else {
+ return null;
+ }
+ }
+
+ public String toString() {
+ return zip.getName();
+ }
+ }
+}
diff --git a/third_party/jmake/src/org/pantsbuild/jmake/CompatibilityChecker.java b/third_party/jmake/src/org/pantsbuild/jmake/CompatibilityChecker.java
new file mode 100644
index 0000000..bd74cfb
--- /dev/null
+++ b/third_party/jmake/src/org/pantsbuild/jmake/CompatibilityChecker.java
@@ -0,0 +1,610 @@
+/* Copyright (c) 2002-2008 Sun Microsystems, Inc. All rights reserved
+ *
+ * This program is distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+package org.pantsbuild.jmake;
+
+import java.lang.reflect.Modifier;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * This class implements checking of source compatibility of classes and supporting operations
+ *
+ * @author Misha Dmitriev
+ * 12 March 2004
+ */
+public class CompatibilityChecker {
+
+ private PCDManager pcdm;
+ private RefClassFinder rf;
+ ClassInfo oldClassInfo = null;
+ ClassInfo newClassInfo = null;
+ private boolean versionsCompatible;
+ private boolean publicConstantChanged;
+
+ public CompatibilityChecker(PCDManager pcdm, boolean failOnDependentJar, boolean noWarnOnDependentJar) {
+ this.pcdm = pcdm;
+ publicConstantChanged = false;
+ rf = new RefClassFinder(pcdm, failOnDependentJar, noWarnOnDependentJar);
+ }
+
+ /**
+ * Compares the two class versions for the given PCDEntry. Returns true if all changes are source
+ * compatible, and false otherwise.
+ */
+ public boolean compareClassVersions(PCDEntry entry) {
+ // I once had the following optimization here with the comment "No sense to make any further checks if
+ // everything is recompiled anyway", but now I believe it's wrong. For each class that was found changed
+ // we need to know whether the new version is compatible with the old or not, since this may determine
+ // whether the new version of this class is promoted into the pdb or not (see PCDManager.updateClassInfoInPCD()).
+ // So, all changed classes should be checked just to correctly determine version compatibility.
+ // if (publicConstantChanged) return false;
+
+ oldClassInfo = pcdm.getClassInfoForPCDEntry(ClassInfo.VER_OLD, entry);
+ newClassInfo = pcdm.getClassInfoForPCDEntry(ClassInfo.VER_NEW, entry);
+
+ rf.initialize(oldClassInfo.name, entry.javaFileFullPath.endsWith(".jar"));
+ versionsCompatible = true;
+
+ checkAccessFlags();
+ checkSuperclasses();
+ checkImplementedInterfaces();
+ checkFields();
+ checkMethodsAndConstructors();
+
+ return versionsCompatible;
+ }
+
+ /** Find all dependent classes for a deleted class. */
+ public void checkDeletedClass(PCDEntry entry) {
+ oldClassInfo = entry.oldClassInfo;
+ rf.initialize(oldClassInfo.name, entry.javaFileFullPath.endsWith(".jar"));
+ rf.findReferencingClassesForDeletedClass(oldClassInfo);
+ // It may happen that the only reference to deleted class X is via "X.class" construct
+ String packageToLookIn =
+ oldClassInfo.isPublic() ? null : oldClassInfo.packageName;
+ rf.findClassesDeclaringField(("class$" + oldClassInfo.name).intern(), "java/lang/Class", true, packageToLookIn);
+ checkForFinalFields();
+ }
+
+ /** Returns the names of classes affected by source incompatible changes to the new version of the checked class. */
+ public String[] getAffectedClasses() {
+ return rf.getAffectedClassNames();
+ }
+
+ /** All of the following methods return true if no source incompatible changes found, and false otherwise */
+ private void checkAccessFlags() {
+ char oldClassFlags = oldClassInfo.accessFlags;
+ char newClassFlags = newClassInfo.accessFlags;
+ if (oldClassFlags == newClassFlags) {
+ return;
+ }
+
+ if (!Modifier.isFinal(oldClassFlags) && Modifier.isFinal(newClassFlags)) {
+ versionsCompatible = false;
+ rf.findDirectSubclasses(oldClassInfo);
+ }
+
+ if (!Modifier.isAbstract(oldClassFlags) && Modifier.isAbstract(newClassFlags)) {
+ versionsCompatible = false;
+ rf.findReferencingClasses0(oldClassInfo);
+ }
+
+ // Now to accessibility modifiers checking...
+ if (Modifier.isPublic(newClassFlags)) {
+ return;
+ }
+
+ if (Modifier.isProtected(newClassFlags)) {
+ if (Modifier.isPublic(oldClassFlags)) {
+ versionsCompatible = false;
+ rf.findDiffPackageAndNotSubReferencingClasses1(oldClassInfo);
+ }
+ } else if (Modifier.isPrivate(newClassFlags)) {
+ if (!Modifier.isPrivate(oldClassFlags)) {
+ versionsCompatible = false;
+ } else {
+ return; // private -> private, nothing more to check
+ }
+ if (Modifier.isPublic(oldClassFlags)) {
+ rf.findReferencingClasses1(oldClassInfo);
+ } else if (Modifier.isProtected(oldClassFlags)) {
+ rf.findThisPackageOrSubReferencingClasses1(oldClassInfo);
+ } else {
+ rf.findThisPackageReferencingClasses1(oldClassInfo);
+ }
+ } else { // newClassFlags has default access, since public has already been excluded
+ if (Modifier.isPublic(oldClassFlags)) {
+ versionsCompatible = false;
+ rf.findDiffPackageReferencingClasses1(oldClassInfo);
+ } else if (Modifier.isProtected(oldClassFlags)) {
+ versionsCompatible = false;
+ rf.findDiffPackageAndSubReferencingClasses1(oldClassInfo);
+ }
+ }
+ }
+
+ private void checkSuperclasses() {
+ List<String> oldSuperNames = oldClassInfo.getAllSuperclassNames();
+ List<String> newSuperNames = newClassInfo.getAllSuperclassNames();
+
+ int oldNamesSizeMinusOne = oldSuperNames.size() - 1;
+ for (int i = 0; i <= oldNamesSizeMinusOne; i++) {
+ String oldSuperName = oldSuperNames.get(i);
+ if (!newSuperNames.contains(oldSuperName)) {
+ versionsCompatible = false;
+ ClassInfo missingSuperClass =
+ pcdm.getClassInfoForName(ClassInfo.VER_OLD, oldSuperName);
+ if (missingSuperClass == null) { // This class is not in project
+ missingSuperClass =
+ ClassPath.getClassInfoForName(oldSuperName, pcdm);
+ if (missingSuperClass == null) {
+ missingSuperClass = new ClassInfo(oldSuperName, pcdm);
+ }
+ }
+ rf.findReferencingClasses2(missingSuperClass, oldClassInfo);
+ }
+ }
+
+ // Now check if the class is an exception, and its kind has changed from unchecked to checked
+ if (oldClassInfo.isInterface() || oldSuperNames.size() == 0) {
+ return;
+ }
+ if (!(oldSuperNames.contains("java/lang/RuntimeException") || oldSuperNames.contains("java/lang/Error"))) {
+ return;
+ }
+ if (!(newSuperNames.contains("java/lang/RuntimeException") || newSuperNames.contains("java/lang/Error"))) {
+ if (!newSuperNames.contains("java/lang/Throwable")) {
+ return;
+ }
+ // Ok, exception kind has changed from unchecked to checked.
+ versionsCompatible = false;
+ rf.findReferencingClasses0(oldClassInfo);
+ rf.findRefsToMethodsThrowingException(oldClassInfo);
+ }
+ }
+
+ private void checkImplementedInterfaces() {
+ Set<String> oldIntfNames = oldClassInfo.getAllImplementedIntfNames();
+ Set<String> newIntfNames = newClassInfo.getAllImplementedIntfNames();
+
+ for (String oldIntfName : oldIntfNames) {
+ if (!newIntfNames.contains(oldIntfName)) {
+ versionsCompatible = false;
+ ClassInfo missingSuperInterface =
+ pcdm.getClassInfoForName(ClassInfo.VER_OLD, oldIntfName);
+ if (missingSuperInterface == null) { // This class is not in project
+ missingSuperInterface =
+ ClassPath.getClassInfoForName(oldIntfName, pcdm);
+ if (missingSuperInterface == null) {
+ missingSuperInterface = new ClassInfo(oldIntfName, pcdm);
+ }
+ }
+ rf.findReferencingClasses2(missingSuperInterface, oldClassInfo);
+ }
+ }
+
+ // Check if the class is abstract, and an interface has been added to its list of implemented interfaces
+ if (newClassInfo.isAbstract()) {
+ for (String newIntfName : newIntfNames) {
+ if (!oldIntfNames.contains(newIntfName)) {
+ versionsCompatible = false;
+ rf.findConcreteSubclasses(oldClassInfo);
+ break;
+ }
+ }
+ }
+ }
+
+ private void checkFields() {
+ String oFNames[] = oldClassInfo.fieldNames;
+ String oFSignatures[] = oldClassInfo.fieldSignatures;
+ char oFFlags[] = oldClassInfo.fieldAccessFlags;
+ String nFNames[] = newClassInfo.fieldNames;
+ String nFSignatures[] = newClassInfo.fieldSignatures;
+ char nFFlags[] = newClassInfo.fieldAccessFlags;
+ int oFLen = oFNames != null ? oFNames.length : 0;
+ int nFLen = nFNames != null ? nFNames.length : 0;
+
+ int oFMod, nFMod;
+ String oFName, oFSig, nFName;
+ int i, j, k, endIdx;
+ int nonMatchingNewFields = nFLen;
+
+ for (i = 0; i < oFLen; i++) {
+ oFMod = oFFlags[i];
+ if (Modifier.isPrivate(oFMod)) {
+ continue; // Changes to private fields don't affect compatibility
+ }
+ oFName = oFNames[i];
+ oFSig = oFSignatures[i];
+ boolean found = false;
+
+ // Look for the same field in the new version considering name and type
+ endIdx = nFLen - 1;
+ k = i < nFLen ? i : endIdx;
+ for (j = 0; j < nFLen; j++) {
+ if (oFName.equals(nFNames[k]) &&
+ oFSig.equals(nFSignatures[k])) {
+ found = true;
+ break;
+ }
+ if (k < endIdx) {
+ k++;
+ } else {
+ k = 0;
+ }
+ }
+
+ if (found) {
+ nonMatchingNewFields--;
+ nFMod = nFFlags[k];
+ checkFieldModifiers(oFMod, nFMod, i, k);
+ if (publicConstantChanged) {
+ return;
+ }
+ } else { // Matching field not found
+ if (Modifier.isStatic(oFMod) && Modifier.isFinal(oFMod) &&
+ oldClassInfo.primitiveConstantInitValues != null &&
+ oldClassInfo.primitiveConstantInitValues[i] != null) {
+ // Compile-time constant deleted
+ versionsCompatible = false;
+ rf.findAllProjectClasses(oldClassInfo, i);
+ if (Modifier.isPublic(oFMod)) {
+ publicConstantChanged = true;
+ return;
+ }
+ } else {
+ versionsCompatible = false;
+ rf.findReferencingClassesForField(oldClassInfo, i);
+ }
+ }
+ }
+
+ if (nonMatchingNewFields > 0) { // There are some fields declared in the new version which don't exist in the old one
+ // Look for fields hiding same-named fields in superclasses
+ for (i = 0; i < nFLen; i++) {
+ nFName = nFNames[i];
+
+ boolean found = false;
+ for (j = 0; j < oFLen; j++) {
+ if (nFName.equals(oFNames[j])) {
+ found = true;
+ break;
+ }
+ }
+ if (found) {
+ continue; // nFName is not an added field
+ }
+ String superName = oldClassInfo.superName;
+ ClassInfo superInfo;
+ while (superName != null) {
+ superInfo =
+ pcdm.getClassInfoForName(ClassInfo.VER_OLD, superName);
+ if (superInfo == null) {
+ break;
+ }
+ String[] superOFNames = superInfo.fieldNames;
+ int superOFNamesLen = superOFNames != null ? superOFNames.length
+ : 0;
+ for (j = 0; j < superOFNamesLen; j++) {
+ if (nFName == superOFNames[j]) {
+ versionsCompatible = false;
+ rf.findReferencingClassesForField(superInfo, j);
+ }
+ }
+ superName = superInfo.superName;
+ }
+ }
+ }
+ }
+
+ /** It is already known that old field is not private */
+ private void checkFieldModifiers(int oFMod, int nFMod, int oldFieldIdx, int newFieldIdx) {
+ if (oFMod == nFMod) {
+ if (Modifier.isFinal(oFMod) &&
+ (!ClassInfo.constFieldInitValuesEqual(oldClassInfo, oldFieldIdx, newClassInfo, newFieldIdx))) {
+ versionsCompatible = false;
+ rf.findAllProjectClasses(oldClassInfo, oldFieldIdx);
+ if (Modifier.isPublic(oFMod)) {
+ publicConstantChanged = true; // Means we will have to recompile ALL project classes
+ }
+ return;
+ }
+ }
+
+ // These tests are ordered such that if a previous test succeeds, there is no need to do further tests, since that
+ // former test will cause more classes to be checked than any of the further tests. That is why it is possible to
+ // check properties that are in fact independent (e.g. accessibility vs. static/non-static) together. But this
+ // optimization only works since all kinds of tests result in the same kind of find..ReferencingClassesForField()
+ // outcome. For methods this is not true, and so there we have to check independent properties separately.
+ if (Modifier.isStatic(oFMod) && Modifier.isFinal(oFMod) && // oFMod is known to be non-private
+ (!Modifier.isFinal(nFMod) || !ClassInfo.constFieldInitValuesEqual(oldClassInfo, oldFieldIdx, newClassInfo, newFieldIdx))) {
+ versionsCompatible = false;
+ rf.findAllProjectClasses(oldClassInfo, oldFieldIdx);
+ if (Modifier.isPublic(oFMod)) {
+ publicConstantChanged = true;
+ }
+ } else if (Modifier.isPrivate(nFMod) || // oFMod is known to be non-private
+ (!Modifier.isFinal(oFMod) && Modifier.isFinal(nFMod)) ||
+ (Modifier.isStatic(oFMod) != Modifier.isStatic(nFMod)) ||
+ (Modifier.isVolatile(oFMod) != Modifier.isVolatile(nFMod))) {
+ versionsCompatible = false;
+ rf.findReferencingClassesForField(oldClassInfo, oldFieldIdx);
+ } else if (Modifier.isPublic(oFMod) && Modifier.isProtected(nFMod)) {
+ versionsCompatible = false;
+ rf.findDiffPackageReferencingClassesForField(oldClassInfo, oldFieldIdx);
+ } else if ((Modifier.isPublic(oFMod) || Modifier.isProtected(oFMod)) &&
+ (!(Modifier.isPublic(nFMod) || Modifier.isProtected(nFMod) || Modifier.isPrivate(nFMod)))) {
+ versionsCompatible = false;
+ if (Modifier.isPublic(oFMod)) {
+ rf.findDiffPackageReferencingClassesForField(oldClassInfo, oldFieldIdx);
+ } else {
+ rf.findDiffPackageAndSubReferencingClassesForField(oldClassInfo, oldFieldIdx);
+ }
+ }
+ }
+
+ private void checkForFinalFields() {
+ char oFFlags[] = oldClassInfo.fieldAccessFlags;
+ int oFLen = oldClassInfo.fieldNames != null ? oldClassInfo.fieldNames.length
+ : 0;
+ int oFMod;
+
+ for (int i = 0; i < oFLen; i++) {
+ oFMod = oFFlags[i];
+ if (Modifier.isPrivate(oFMod)) {
+ continue; // Changes to private fields don't affect compatibility
+ }
+ if (Modifier.isStatic(oFMod) && Modifier.isFinal(oFMod)) {
+ rf.findAllProjectClasses(oldClassInfo, i);
+ if (Modifier.isPublic(oFMod)) {
+ publicConstantChanged = true;
+ return;
+ }
+ }
+ }
+ }
+
+ private void checkMethodsAndConstructors() {
+ String oMNames[] = oldClassInfo.methodNames;
+ String oMSignatures[] = oldClassInfo.methodSignatures;
+ char oMFlags[] = oldClassInfo.methodAccessFlags;
+ String nMNames[] = newClassInfo.methodNames;
+ String nMSignatures[] = newClassInfo.methodSignatures;
+ char nMFlags[] = newClassInfo.methodAccessFlags;
+ int oMLen = oMNames != null ? oMNames.length : 0;
+ int nMLen = nMNames != null ? nMNames.length : 0;
+
+ int oMMod, nMMod;
+ String oMName, oMSig, nMName, nMSig;
+ int i, j, k, endIdx;
+ int nonMatchingNewMethods = nMLen;
+
+ for (i = 0; i < oMLen; i++) {
+ oMMod = oMFlags[i];
+ if (Modifier.isPrivate(oMMod)) {
+ continue; // Changes to private methods don't affect compatibility
+ }
+ oMName = oMNames[i];
+ oMSig = oMSignatures[i];
+ boolean found = false;
+
+ // Look for the same method in the new version considering name and signature
+ endIdx = nMLen - 1;
+ k = i < nMLen ? i : endIdx;
+ for (j = 0; j < nMLen; j++) {
+ if (oMName == nMNames[k] && oMSig == nMSignatures[k]) {
+ found = true;
+ break;
+ }
+ if (k < endIdx) {
+ k++;
+ } else {
+ k = 0;
+ }
+ }
+
+ if (found) {
+ nonMatchingNewMethods--;
+ nMMod = nMFlags[k];
+ if (oMMod != nMMod) {
+ checkMethodModifiers(oMMod, nMMod, i);
+ }
+
+ // Check if the new method throws more exceptions than the old one
+ if (newClassInfo.checkedExceptions != null && newClassInfo.checkedExceptions[k] != null) {
+ if (oldClassInfo.checkedExceptions == null) {
+ versionsCompatible = false;
+ rf.findReferencingClassesForMethod(oldClassInfo, i);
+ } else if (oldClassInfo.checkedExceptions[i] == null) {
+ versionsCompatible = false;
+ rf.findReferencingClassesForMethod(oldClassInfo, i);
+ } else {
+ String oldExceptions[] =
+ oldClassInfo.checkedExceptions[i];
+ String newExceptions[] =
+ newClassInfo.checkedExceptions[k];
+ for (int ei = 0; ei < newExceptions.length; ei++) {
+ String newEx = newExceptions[ei];
+ found = false;
+ for (int ej = 0; ej < oldExceptions.length; ej++) {
+ if (newEx.equals(oldExceptions[ej])) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ versionsCompatible = false;
+ rf.findReferencingClassesForMethod(oldClassInfo, i);
+ break;
+ }
+ }
+ }
+ }
+ } else { // Matching method not found
+ versionsCompatible = false;
+ rf.findReferencingClassesForMethod(oldClassInfo, i);
+ // Deleting a concrete method from an abstract class is a special case
+ if (oldClassInfo.isAbstract() && !Modifier.isAbstract(oMMod)) {
+ rf.findConcreteSubclassesNotOverridingAbstractMethod(oldClassInfo, oldClassInfo, i);
+ }
+ }
+ }
+
+ if (nonMatchingNewMethods > 0) { // There are some methods/constructors declared in the new version which don't exist in the old one
+ if (!oldClassInfo.isInterface()) {
+ for (i = 0; i < nMLen; i++) {
+ nMMod = nMFlags[i];
+ if (Modifier.isPrivate(nMMod)) {
+ continue;
+ }
+ String newMName = nMNames[i];
+ final String newMSig = nMSignatures[i];
+ final boolean isStatic = Modifier.isStatic(nMMod);
+
+ boolean found = false;
+ for (j = 0; j < oMLen; j++) {
+ if (newMName.equals(oMNames[j]) &&
+ newMSig.equals(oMSignatures[j])) {
+ found = true;
+ break;
+ }
+ }
+ if (found) {
+ continue; // nMName is not an added method
+ }
+ // Check if the new method is a static one that hides an inherited static method
+ // Check if the new method overloads an existing (declared or inherited) method. Overloading test is rough -
+ // we just check if the number of parameters is the same. Note that if a new constructor has been added, it
+ // can be treated in the same way, except that we shouldn't look up "same name methods" for it in superclasses.
+ oldClassInfo.findExistingSameNameMethods(newMName,
+ !newMName.equals("<init>"), false,
+ new ClassInfo.MethodHandler() {
+
+ void handleMethod(ClassInfo classInfo, int methodIdx) {
+ String otherMSig =
+ classInfo.methodSignatures[methodIdx];
+ if ((newMSig.equals(otherMSig) && isStatic &&
+ classInfo != oldClassInfo) ||
+ (newMSig != otherMSig &&
+ Utils.sameParamNumber(newMSig, otherMSig))) {
+ versionsCompatible = false;
+ rf.findReferencingClassesForMethod(classInfo, methodIdx);
+ }
+ }
+ });
+
+ if (Modifier.isAbstract(nMMod)) {
+ // An abstract method added to the class. Find any concrete subclasses that don't override
+ // or inherit a concrete implementation of this method.
+ versionsCompatible = false;
+ rf.findConcreteSubclassesNotOverridingAbstractMethod(oldClassInfo, newClassInfo, i);
+ }
+ // Check if there is a method with the same name in some subclass, such that it now overrides
+ // or overloads the added method.
+ if (subclassesDeclareSameNameMethod(oldClassInfo, newMName)) {
+ versionsCompatible = false;
+ }
+ }
+ } else { // We are checking an interface.
+ for (i = 0; i < nMLen; i++) {
+ String newMName = nMNames[i];
+ final String newMSig = nMSignatures[i];
+
+ boolean found = false;
+ for (j = 0; j < oMLen; j++) {
+ if (newMName == oMNames[j] && newMSig == oMSignatures[j]) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ versionsCompatible = false;
+
+ // Check if the new method overloads an existing (declared or inherited) method. Overloading test is rough -
+ // we just check if the number of parameters is the same.
+ oldClassInfo.findExistingSameNameMethods(newMName, true, true, new ClassInfo.MethodHandler() {
+
+ void handleMethod(ClassInfo classInfo, int methodIdx) {
+ String otherMSig =
+ classInfo.methodSignatures[methodIdx];
+ if (newMSig != otherMSig &&
+ Utils.sameParamNumber(newMSig, otherMSig)) {
+ rf.findReferencingClassesForMethod(classInfo, methodIdx);
+ }
+ }
+ });
+
+ rf.findDirectlyAndOtherwiseImplementingConcreteClasses(oldClassInfo);
+ rf.findAbstractSubtypesWithSameNameMethod(oldClassInfo, newMName, newMSig);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ private void checkMethodModifiers(int oMMod, int nMMod, int oldMethodIdx) {
+ if (Modifier.isPrivate(nMMod)) {
+ versionsCompatible = false;
+ rf.findReferencingClassesForMethod(oldClassInfo, oldMethodIdx);
+ } else if (Modifier.isPublic(oMMod) && Modifier.isProtected(nMMod)) {
+ versionsCompatible = false;
+ rf.findDiffPackageReferencingClassesForMethod(oldClassInfo, oldMethodIdx);
+ } else if ((Modifier.isPublic(oMMod) || Modifier.isProtected(oMMod)) &&
+ (!(Modifier.isPublic(nMMod) || Modifier.isProtected(nMMod) || Modifier.isPrivate(nMMod)))) {
+ versionsCompatible = false;
+ if (Modifier.isPublic(oMMod)) {
+ rf.findDiffPackageReferencingClassesForMethod(oldClassInfo, oldMethodIdx);
+ } else {
+ rf.findDiffPackageAndSubReferencingClassesForMethod(oldClassInfo, oldMethodIdx);
+ }
+ } else if ((Modifier.isPrivate(oMMod) && !Modifier.isPrivate(nMMod)) ||
+ (Modifier.isProtected(oMMod) && Modifier.isPublic(nMMod)) ||
+ (!(Modifier.isPublic(oMMod) || Modifier.isProtected(oMMod) || Modifier.isPrivate(oMMod)) &&
+ (Modifier.isPublic(nMMod) || Modifier.isProtected(nMMod)))) {
+ versionsCompatible = false;
+ rf.findSubclassesReimplementingMethod(oldClassInfo, oldMethodIdx);
+ }
+
+ if ((!Modifier.isAbstract(oMMod) && Modifier.isAbstract(nMMod)) ||
+ (Modifier.isStatic(oMMod) != Modifier.isStatic(nMMod))) {
+ versionsCompatible = false;
+ rf.findReferencingClassesForMethod(oldClassInfo, oldMethodIdx);
+ if (!Modifier.isAbstract(oMMod) && Modifier.isAbstract(nMMod)) {
+ rf.findConcreteSubclassesNotOverridingAbstractMethod(oldClassInfo, newClassInfo, oldMethodIdx);
+ }
+ }
+ if (!Modifier.isFinal(oMMod) && Modifier.isFinal(nMMod)) {
+ versionsCompatible = false;
+ rf.findSubclassesReimplementingMethod(oldClassInfo, oldMethodIdx);
+ }
+ }
+
+ /**
+ * Returns true if any subclass(es), direct or indirect, declare a method with name methodName.
+ * For each such occurence, referencing classes are looked up and added to the list of affected classes.
+ */
+ private boolean subclassesDeclareSameNameMethod(ClassInfo oldClassInfo, String methodName) {
+ boolean res = false;
+ ClassInfo[] directSubclasses = oldClassInfo.getDirectSubclasses();
+ for (int i = 0; i < directSubclasses.length; i++) {
+ ClassInfo subclass = directSubclasses[i];
+ int methNo = subclass.declaresSameNameMethod(methodName);
+ if (methNo >= 0) {
+ rf.addToAffectedClassNames(subclass.name);
+ rf.findReferencingClassesForMethod(subclass, methNo);
+ res = true;
+ }
+ if (subclassesDeclareSameNameMethod(subclass, methodName)) {
+ res = true;
+ }
+ }
+ return res;
+ }
+}
diff --git a/third_party/jmake/src/org/pantsbuild/jmake/Main.java b/third_party/jmake/src/org/pantsbuild/jmake/Main.java
new file mode 100644
index 0000000..0e80554
--- /dev/null
+++ b/third_party/jmake/src/org/pantsbuild/jmake/Main.java
@@ -0,0 +1,899 @@
+/* Copyright (c) 2002-2008 Sun Microsystems, Inc. All rights reserved
+ *
+ * This program is distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+package org.pantsbuild.jmake;
+
+import java.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.Reader;
+import java.io.StreamTokenizer;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The main class of the <b>jmake</b> tool.<p>
+ *
+ * Has several entrypoints: <code>main</code>, <code>mainExternal</code>, <code>mainProgrammatic</code>,
+ * <code>mainExternalControlled</code> and <code>mainProgrammaticControlled</code>.
+ * The first is not intended to be used by applications other than <b>jmake</b> itself, whereas the
+ * rest can be used to call <b>jmake</b> externally with various degrees of control over its behaviour.
+ * See method comments for more details.
+ *
+ * @author Misha Dmitriev
+ * 12 October 2004
+ */
+public class Main {
+
+ static final String DEFAULT_STORE_NAME = "jmake.pdb";
+ static final String VERSION = "1.3.8-11";
+ private String pdbFileName = null;
+ private List<String> allProjectJavaFileNamesList =
+ new ArrayList<String>(100);
+ private String allProjectJavaFileNames[];
+ private String addedJavaFileNames[], removedJavaFileNames[], updatedJavaFileNames[];
+ private String destDir = "";
+ private List<String> javacAddArgs = new ArrayList<String>();
+ private String jcPath, jcMainClass, jcMethod;
+ private String jcExecApp;
+ boolean controlledExecution = false;
+ Object externalApp = null;
+ Method externalCompileSourceFilesMethod = null;
+ private boolean failOnDependentJar = false, noWarnOnDependentJar = false;
+ private boolean pdbTextFormat = false;
+ private PCDManager pcdm = null;
+ private String dependencyFile;
+ private static final String optNames[] = {"-h", "-help", "-d", "-pdb", "-C", "-jcpath", "-jcmainclass", "-jcmethod", "-jcexec", "-Xtiming", "-version",
+ "-warnlimit", "-failondependentjar", "-nowarnondependentjar", "-classpath", "-projclasspath", "-bootclasspath", "-extdirs", "-vpath", "-pdb-text-format",
+ "-depfile"};
+ private static final int optArgs[] = {0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1};
+ private static final int OPT_H = 0;
+ private static final int OPT_HELP = 1;
+ private static final int OPT_D = 2;
+ private static final int OPT_STORE = 3;
+ private static final int OPT_JAVAC_OPT = 4;
+ private static final int OPT_JCPATH = 5;
+ private static final int OPT_JCMAINCLASS = 6;
+ private static final int OPT_JCMETHOD = 7;
+ private static final int OPT_JCEXEC = 8;
+ private static final int OPT_TIMING = 9;
+ private static final int OPT_VERSION = 10;
+ private static final int OPT_WARNLIMIT = 11;
+ private static final int OPT_FAILONDEPJAR = 12;
+ private static final int OPT_NOWARNONDEPJAR = 13;
+ private static final int OPT_CLASSPATH = 14;
+ private static final int OPT_PROJECTCLASSPATH = 15;
+ private static final int OPT_BOOTCLASSPATH = 16;
+ private static final int OPT_EXTDIRS = 17;
+ private static final int OPT_VPATH = 18;
+ private static final int OPT_PDB_TEXT_FORMAT = 19;
+ private static final int OPT_DEPENDENCY_FILE = 20;
+
+ /** Construct a new instance of Main. */
+ public Main() {
+ }
+
+ /**
+ * Checks whether the argument is a legal jmake option. Returns its number or
+ * -1 if not found.
+ */
+ private static int inOptions(String option) {
+ if (option.startsWith(optNames[OPT_JAVAC_OPT])) {
+ return OPT_JAVAC_OPT;
+ }
+ for (int i = 0; i < optNames.length; i++) {
+ if (option.equals(optNames[i])) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private void processCommandLine(String args[]) {
+ if ((args.length == 0) || (args.length == 1 && args[0].equals(optNames[OPT_HELP]))) {
+ printUsage();
+ throw new PrivateException(new PublicExceptions.NoActionRequestedException());
+ }
+
+ List<String> argsV = new ArrayList<String>();
+ for (int i = 0; i < args.length; i++) {
+ argsV.add(args[i]);
+ }
+ int argsSt = 0;
+ String arg;
+
+ while (argsSt < argsV.size()) {
+ arg = argsV.get(argsSt++);
+ if (arg.charAt(0) == '-') {
+ int argN = inOptions(arg);
+ if (argN != -1) {
+ if (argsSt + optArgs[argN] > argsV.size()) {
+ optRequiresArg("\"" + optNames[argN] + "\"");
+ }
+ } else {
+ bailOut(arg + ERR_IS_INVALID_OPTION);
+ }
+
+ switch (argN) {
+ case OPT_H:
+ case OPT_HELP:
+ printUsage();
+ throw new PrivateException(new PublicExceptions.NoActionRequestedException());
+ case OPT_D:
+ destDir = argsV.get(argsSt);
+ javacAddArgs.add("-d");
+ javacAddArgs.add(argsV.get(argsSt));
+ argsSt++;
+ break;
+ case OPT_CLASSPATH:
+ try {
+ setClassPath(argsV.get(argsSt++));
+ } catch (PublicExceptions.InvalidCmdOptionException ex) {
+ bailOut(ex.getMessage());
+ }
+ break;
+ case OPT_PROJECTCLASSPATH:
+ try {
+ setProjectClassPath(argsV.get(argsSt++));
+ } catch (PublicExceptions.InvalidCmdOptionException ex) {
+ bailOut(ex.getMessage());
+ }
+ break;
+ case OPT_STORE:
+ pdbFileName = argsV.get(argsSt++);
+ break;
+ case OPT_JAVAC_OPT:
+ String javacArg =
+ (argsV.get(argsSt - 1)).substring(2);
+ if (javacArg.equals("-d") ||
+ javacArg.equals("-classpath") ||
+ javacArg.equals("-bootclasspath") ||
+ javacArg.equals("-extdirs")) {
+ bailOut(javacArg + ERR_SHOULD_BE_EXPLICIT);
+ }
+ javacAddArgs.add(javacArg);
+ break;
+ case OPT_JCPATH:
+ if (jcExecApp != null) {
+ bailOut(ERR_NO_TWO_COMPILER_OPTIONS);
+ }
+ jcPath = argsV.get(argsSt++);
+ break;
+ case OPT_JCMAINCLASS:
+ if (jcExecApp != null) {
+ bailOut(ERR_NO_TWO_COMPILER_OPTIONS);
+ }
+ jcMainClass = argsV.get(argsSt++);
+ break;
+ case OPT_JCMETHOD:
+ if (jcExecApp != null) {
+ bailOut(ERR_NO_TWO_COMPILER_OPTIONS);
+ }
+ jcMethod = argsV.get(argsSt++);
+ break;
+ case OPT_JCEXEC:
+ if (jcPath != null || jcMainClass != null || jcMethod != null) {
+ bailOut(ERR_NO_TWO_COMPILER_OPTIONS);
+ }
+ jcExecApp = argsV.get(argsSt++);
+ break;
+ case OPT_TIMING:
+ Utils.setTimingOn();
+ break;
+ case OPT_VERSION:
+ // Utils.printInfoMessage("jmake version " + VERSION); // Printed anyway at present...
+ throw new PrivateException(new PublicExceptions.NoActionRequestedException());
+ case OPT_WARNLIMIT:
+ try {
+ Utils.warningLimit =
+ Integer.parseInt(argsV.get(argsSt++));
+ } catch (NumberFormatException e) {
+ Utils.ignore(e);
+ bailOut(argsV.get(argsSt) + ERR_IS_INVALID_OPTION);
+ }
+ break;
+ case OPT_FAILONDEPJAR:
+ if (noWarnOnDependentJar) {
+ bailOut("it is not allowed to use -nowarnondependentjar and -failondependentjar together");
+ }
+ failOnDependentJar = true;
+ break;
+ case OPT_NOWARNONDEPJAR:
+ if (failOnDependentJar) {
+ bailOut("it is not allowed to use -failondependentjar and -nowarnondependentjar together");
+ }
+ noWarnOnDependentJar = true;
+ break;
+ case OPT_BOOTCLASSPATH:
+ try {
+ setBootClassPath(argsV.get(argsSt++));
+ } catch (PublicExceptions.InvalidCmdOptionException ex) {
+ bailOut(ex.getMessage());
+ }
+ break;
+ case OPT_EXTDIRS:
+ try {
+ setExtDirs(argsV.get(argsSt++));
+ } catch (PublicExceptions.InvalidCmdOptionException ex) {
+ bailOut(ex.getMessage());
+ }
+ break;
+ case OPT_VPATH:
+ try {
+ setVirtualPath(argsV.get(argsSt++));
+ } catch (PublicExceptions.InvalidCmdOptionException ex) {
+ bailOut(ex.getMessage());
+ }
+ break;
+ case OPT_PDB_TEXT_FORMAT:
+ pdbTextFormat = true;
+ break;
+ case OPT_DEPENDENCY_FILE:
+ dependencyFile = argsV.get(argsSt++);
+ break;
+ default:
+ bailOut(arg + ERR_IS_INVALID_OPTION);
+ }
+ } else { // Not an option, at least does not start with "-". Treat it as a command line file name or source name.
+ if (arg.length() > 1 && arg.charAt(0) == '@') {
+ arg = arg.substring(1);
+ loadCmdFile(arg, argsV);
+ } else {
+ if (!arg.endsWith(".java")) {
+ bailOut(arg + ERR_IS_INVALID_OPTION);
+ }
+ allProjectJavaFileNamesList.add(arg);
+ }
+ }
+ }
+ }
+
+ /** Load @-file that can contain anything that command line can contain. */
+ private void loadCmdFile(String name, List<String> argsV) {
+ try {
+ Reader r = new BufferedReader(new FileReader(name));
+ StreamTokenizer st = new StreamTokenizer(r);
+ st.resetSyntax();
+ st.wordChars(' ', 255);
+ st.whitespaceChars(0, ' ');
+ st.commentChar('#');
+ st.quoteChar('"');
+ st.quoteChar('\'');
+ while (st.nextToken() != StreamTokenizer.TT_EOF) {
+ argsV.add(st.sval);
+ }
+ r.close();
+ } catch (IOException e) {
+ throw new PrivateException(new PublicExceptions.CommandFileReadException(name + ":\n" + e.getMessage()));
+ }
+ }
+
+ /**
+ * Main entrypoint for applications that want to call <b>jmake</b> externally and are willing
+ * to handle exceptions that it may throw.
+ *
+ * @param args command line arguments passed to <b>jmake</b>.
+ *
+ * @throws PublicExceptions.NoActionRequestedException if <b>jmake</b> was not requested to do any real work;
+ * @throws PublicExceptions.InvalidCmdOptionException if invalid command line option was detected;
+ * @throws PublicExceptions.PDBCorruptedException if project database is corrupted;
+ * @throws PublicExceptions.CommandFileReadException if there was error reading a command file;
+ * @throws PublicExceptions.CompilerInteractionException if there was a problem initializing or calling the compiler,
+ * or compilation errors were detected;
+ * @throws PublicExceptions.ClassFileParseException if there was error parsing a class file;
+ * @throws PublicExceptions.ClassNameMismatchException if there is a mismatch between the deduced and the actual class name;
+ * @throws PublicExceptions.InvalidSourceFileExtensionException if a supplied source file has an invalid extension (not .java);
+ * @throws PublicExceptions.JarDependsOnSourceException if a class in a <code>JAR</code> is found dependent on a class with the .java source;
+ * @throws PublicExceptions.DoubleEntryException if more than one entry for the same class is found in the project
+ * @throws FileNotFoundException if a <code>.java</code> or a <code>.class</code> file was not found;
+ * @throws IOException if there was an I/O problem of any kind;
+ * @throws PublicExceptions.InternalException if an internal problem that should never happen was detected.
+ */
+ public void mainProgrammatic(String args[]) throws
+ PublicExceptions.NoActionRequestedException,
+ PublicExceptions.InvalidCmdOptionException,
+ PublicExceptions.PDBCorruptedException,
+ PublicExceptions.CommandFileReadException,
+ PublicExceptions.CompilerInteractionException,
+ PublicExceptions.ClassFileParseException,
+ PublicExceptions.ClassNameMismatchException,
+ PublicExceptions.InvalidSourceFileExtensionException,
+ PublicExceptions.JarDependsOnSourceException,
+ PublicExceptions.DoubleEntryException,
+ FileNotFoundException,
+ IOException,
+ PublicExceptions.InternalException {
+ try {
+ Utils.printInfoMessage("Jmake version " + VERSION);
+ if (!controlledExecution) {
+ processCommandLine(args);
+ String[] projectJars = ClassPath.getProjectJars();
+ if (projectJars != null) {
+ for (int i = 0; i < projectJars.length; i++) {
+ allProjectJavaFileNamesList.add(projectJars[i]);
+ }
+ }
+ allProjectJavaFileNames =
+ allProjectJavaFileNamesList.toArray(new String[allProjectJavaFileNamesList.size()]);
+ } else {
+ String[] projectJars = ClassPath.getProjectJars();
+ if (projectJars != null) {
+ String newNames[] =
+ new String[allProjectJavaFileNames.length + projectJars.length];
+ System.arraycopy(allProjectJavaFileNames, 0, newNames, 0, allProjectJavaFileNames.length);
+ System.arraycopy(projectJars, 0, newNames, allProjectJavaFileNames.length, projectJars.length);
+ allProjectJavaFileNames = newNames;
+ }
+ }
+
+ Utils.startTiming(Utils.TIMING_PDBREAD);
+ PCDContainer pcdc;
+ pcdc = PCDContainer.load(pdbFileName, pdbTextFormat);
+ Utils.stopAndPrintTiming("DB read", Utils.TIMING_PDBREAD);
+
+ pcdm = new PCDManager(pcdc, allProjectJavaFileNames,
+ addedJavaFileNames, removedJavaFileNames, updatedJavaFileNames,
+ destDir, javacAddArgs, failOnDependentJar, noWarnOnDependentJar,
+ dependencyFile);
+
+ pcdm.initializeCompiler(jcExecApp, jcPath, jcMainClass, jcMethod, externalApp, externalCompileSourceFilesMethod);
+
+ pcdm.run();
+ } catch (PrivateException e) {
+ Throwable origException = e.getOriginalException();
+ if (origException instanceof PublicExceptions.NoActionRequestedException) {
+ throw (PublicExceptions.NoActionRequestedException) origException;
+ } else if (origException instanceof PublicExceptions.InvalidCmdOptionException) {
+ throw (PublicExceptions.InvalidCmdOptionException) origException;
+ } else if (origException instanceof PublicExceptions.CommandFileReadException) {
+ throw (PublicExceptions.CommandFileReadException) origException;
+ } else if (origException instanceof PublicExceptions.PDBCorruptedException) {
+ throw (PublicExceptions.PDBCorruptedException) origException;
+ } else if (origException instanceof PublicExceptions.CompilerInteractionException) {
+ throw (PublicExceptions.CompilerInteractionException) origException;
+ } else if (origException instanceof PublicExceptions.ClassFileParseException) {
+ throw (PublicExceptions.ClassFileParseException) origException;
+ } else if (origException instanceof PublicExceptions.ClassNameMismatchException) {
+ throw (PublicExceptions.ClassNameMismatchException) origException;
+ } else if (origException instanceof PublicExceptions.InvalidSourceFileExtensionException) {
+ throw (PublicExceptions.InvalidSourceFileExtensionException) origException;
+ } else if (origException instanceof PublicExceptions.JarDependsOnSourceException) {
+ throw (PublicExceptions.JarDependsOnSourceException) origException;
+ } else if (origException instanceof PublicExceptions.DoubleEntryException) {
+ throw (PublicExceptions.DoubleEntryException) origException;
+ } else if (origException instanceof FileNotFoundException) {
+ throw (FileNotFoundException) origException;
+ } else if (origException instanceof IOException) {
+ throw (IOException) origException;
+ } else if (origException instanceof PublicExceptions.InternalException) {
+ throw (PublicExceptions.InternalException) origException;
+ }
+ } finally {
+ ClassPath.resetOnFinish();
+ }
+ }
+
+ /**
+ * Main entrypoint for applications that want to call <b>jmake</b> externally and would prefer
+ * receiving an error code instead of an exception in case something goes wrong.<p>
+ *
+ * @param args command line arguments passed to <b>jmake</b>.
+ *
+ * @return <dl>
+ * <dt><code> 0</code> if everything was successful;
+ * <dt><code> -1</code> invalid command line option detected;
+ * <dt><code> -2</code> error reading command file;
+ * <dt><code> -3</code> project database corrupted;
+ * <dt><code> -4</code> error initializing or calling the compiler;
+ * <dt><code> -5</code> compilation error;
+ * <dt><code> -6</code> error parsing a class file;
+ * <dt><code> -7</code> file not found;
+ * <dt><code> -8</code> I/O exception;
+ * <dt><code> -9</code> internal jmake exception;
+ * <dt><code>-10</code> deduced and actual class name mismatch;
+ * <dt><code>-11</code> invalid source file extension;
+ * <dt><code>-12</code> a class in a <code>JAR</code> is found dependent on a class with the .java source;
+ * <dt><code>-13</code> more than one entry for the same class is found in the project
+ * <dt><code>-20</code> internal Java error (caused by <code>java.lang.InternalError</code>);
+ * <dt><code>-30</code> internal Java error (caused by <code>java.lang.RuntimeException</code>).
+ * </dl>
+ */
+ public int mainExternal(String args[]) {
+ try {
+ mainProgrammatic(args);
+ } catch (PublicExceptions.NoActionRequestedException e0) {
+ // Nothing to do
+ } catch (PublicExceptions.InvalidCmdOptionException e1) {
+ Utils.printErrorMessage(e1.getMessage());
+ return -1;
+ } catch (PublicExceptions.CommandFileReadException e2) {
+ Utils.printErrorMessage("error parsing command file:");
+ Utils.printErrorMessage(e2.getMessage());
+ return -2;
+ } catch (PublicExceptions.PDBCorruptedException e3) {
+ Utils.printErrorMessage("project database corrupted: " + e3.getMessage());
+ return -3;
+ } catch (PublicExceptions.CompilerInteractionException e4) {
+ if (e4.getOriginalException() != null) {
+ Utils.printErrorMessage("error interacting with the compiler: ");
+ Utils.printErrorMessage(e4.getMessage());
+ Utils.printErrorMessage("original exception:");
+ Utils.printErrorMessage(e4.getOriginalException().getMessage());
+ return -4;
+ } else { // Otherwise there is a compilation error, and the compiler has already printed a lot...
+ return -5;
+ }
+ } catch (PublicExceptions.ClassFileParseException e6) {
+ Utils.printErrorMessage(e6.getMessage());
+ return -6;
+ } catch (FileNotFoundException e7) {
+ Utils.printErrorMessage(e7.getMessage());
+ return -7;
+ } catch (IOException e8) {
+ Utils.printErrorMessage(e8.getMessage());
+ return -8;
+ } catch (PublicExceptions.InternalException e9) {
+ Utils.printErrorMessage("internal jmake exception detected:");
+ Utils.printErrorMessage(e9.getMessage());
+ Utils.printErrorMessage(Utils.REPORT_PROBLEM);
+ Utils.printErrorMessage("the stack trace is as follows:");
+ e9.printStackTrace();
+ return -9;
+ } catch (PublicExceptions.ClassNameMismatchException e10) {
+ Utils.printErrorMessage(e10.getMessage());
+ return -10;
+ } catch (PublicExceptions.InvalidSourceFileExtensionException e11) {
+ Utils.printErrorMessage(e11.getMessage());
+ return -11;
+ } catch (PublicExceptions.JarDependsOnSourceException e12) {
+ Utils.printErrorMessage(e12.getMessage());
+ return -12;
+ } catch (PublicExceptions.DoubleEntryException e13) {
+ Utils.printErrorMessage(e13.getMessage());
+ return -13;
+ } catch (InternalError e20) {
+ Utils.printErrorMessage("internal Java error: " + e20);
+ Utils.printErrorMessage("Consult the following stack trace for more info:");
+ e20.printStackTrace();
+ return -20;
+ } catch (RuntimeException e30) {
+ Utils.printErrorMessage("internal Java exception: " + e30);
+ Utils.printErrorMessage("Consult the following stack trace for more info:");
+ e30.printStackTrace();
+ return -30;
+ }
+
+ return 0;
+ }
+
+ /**
+ * Main entrypoint for applications such as Ant, that want to have full control over
+ * compilations that <b>jmake</b> invokes, and are willing to handle exceptions
+ * that it may throw.
+ *
+ * @param javaFileNames array of strings that specify <code>.java</code> file names.
+ * @param destDirName name of the destination directory (<b>jmake</b> will look up binary classes
+ * in there, it should be the same as the one used by the Java compiler method).
+ * If <code>null</code> is passed, classes will be looked up in the same directories
+ * as their sources, in agreement with the default Java compiler behaviour.
+ * @param pdbFileName project database file name (if <code>null</code> is passed,
+ * a file with the default name placed in the current directory will be used).
+ * @param externalApp an object on which to invoke <code>externalCompileSourceFilesMethod</code> method.
+ * @param externalCompileSourceFilesMethod a method of the form <code>int x(String[] args)</code>. It
+ * should return <code>0</code> if compilation is successful and any non-zero value
+ * otherwise. <b>jmake</b> passes it a list of the <code>.java</code> files to
+ * recompile in the form of canonical full path file names.
+ *
+ * @throws PublicExceptions.NoActionRequestedException if <b>jmake</b> was not requested to do any real work;
+ * @throws PublicExceptions.InvalidCmdOptionException if invalid command line option was detected;
+ * @throws PublicExceptions.PDBCorruptedException if project database is corrupted;
+ * @throws PublicExceptions.CommandFileReadException if there was error reading a command file;
+ * @throws PublicExceptions.CompilerInteractionException if there was a problem initializing or calling the compiler,
+ * or compilation errors were detected;
+ * @throws PublicExceptions.ClassFileParseException if there was error parsing a class file;
+ * @throws PublicExceptions.ClassNameMismatchException if there is a mismatch between the deduced and the actual class name;
+ * @throws PublicExceptions.InvalidSourceFileExtensionException if a specified source file has an invalid extension (not .java);
+ * @throws PublicExceptions.JarDependsOnSourceException if a class in a <code>JAR</code> is found dependent on a class with the .java source;
+ * @throws PublicExceptions.DoubleEntryException if more than one entry for the same class is found in the project
+ * @throws PublicExceptions.InternalException if an internal problem that should never happen was detected.
+ * @throws FileNotFoundException if a <code>.java</code> or a <code>.class</code> file was not found;
+ * @throws IOException if there was an I/O problem of any kind;
+ */
+ public void mainProgrammaticControlled(String javaFileNames[], String destDirName, String pdbFileName,
+ Object externalApp, Method externalCompileSourceFilesMethod) throws
+ PublicExceptions.NoActionRequestedException,
+ PublicExceptions.InvalidCmdOptionException,
+ PublicExceptions.PDBCorruptedException,
+ PublicExceptions.CommandFileReadException,
+ PublicExceptions.CompilerInteractionException,
+ PublicExceptions.ClassFileParseException,
+ PublicExceptions.ClassNameMismatchException,
+ PublicExceptions.InvalidSourceFileExtensionException,
+ PublicExceptions.JarDependsOnSourceException,
+ PublicExceptions.DoubleEntryException,
+ PublicExceptions.InternalException,
+ FileNotFoundException,
+ IOException {
+
+ controlledExecution = true;
+ this.pdbFileName = pdbFileName;
+ this.destDir = destDirName;
+ this.allProjectJavaFileNames = javaFileNames;
+ this.externalApp = externalApp;
+ this.externalCompileSourceFilesMethod = externalCompileSourceFilesMethod;
+
+ mainProgrammatic(null);
+ }
+
+ /**
+ * Main entrypoint for applications such as Ant, that want to have full control over
+ * compilations that <b>jmake</b> invokes, and do not want to handle exceptions that it
+ * may throw. Error codes returned are the same as <code>mainExternal(String[])</code> returns.
+ *
+ * @param javaFileNames array of strings that specify <code>.java</code> file names.
+ * @param destDirName name of the destination directory (<b>jmake</b> will look up binary classes
+ * in there, it should be the same as the one used by the Java compiler method).
+ * If <code>null</code> is passed, classes will be looked up in the same directories
+ * as their sources, in agreement with the default Java compiler behaviour.
+ * @param pdbFileName project database file name (if <code>null</code> is passed,
+ * a file with the default name placed in the current directory will be used).
+ * @param externalApp an object on which to invoke <code>externalCompileSourceFilesMethod</code> method.
+ * @param externalCompileSourceFilesMethod a method of the form <code>int x(String[] args)</code>. It
+ * should return <code>0</code> if compilation is successful and any non-zero value
+ * otherwise. <b>jmake</b> passes it a list of the <code>.java</code> files to
+ * recompile in the form of canonical full path file names.
+ *
+ * @see #mainExternal(String[])
+ */
+ public int mainExternalControlled(String javaFileNames[], String destDirName, String pdbFileName,
+ Object externalApp, Method externalCompileSourceFilesMethod) {
+ controlledExecution = true;
+ this.pdbFileName = pdbFileName;
+ this.destDir = destDirName;
+ this.allProjectJavaFileNames = javaFileNames;
+ this.externalApp = externalApp;
+ this.externalCompileSourceFilesMethod = externalCompileSourceFilesMethod;
+
+ return mainExternal(null);
+ }
+
+ /**
+ * Main entrypoint for applications such as IDEs, that themselves keep track of updated/added/removed sources,
+ * want to have full control over compilations that <b>jmake</b> invokes, and are willing to handle exceptions
+ * that it may throw.
+ *
+ * @param addedJavaFileNames names of <code>.java</code> files just added to the project
+ * @param removedJavaFileNames names of <code>.java</code> files just removed from the project
+ * @param updatedJavaFileNames names of updated project <code>.java</code> files
+ * @param destDirName name of the destination directory (<b>jmake</b> will look up binary classes
+ * in there, it should be the same as the one used by the Java compiler method).
+ * If <code>null</code> is passed, classes will be looked up in the same directories
+ * as their sources, in agreement with the default Java compiler behaviour.
+ * @param pdbFileName project database file name (if <code>null</code> is passed,
+ * a file with the default name placed in the current directory will be used).
+ * @param externalApp an object on which to invoke <code>externalCompileSourceFilesMethod</code> method.
+ * @param externalCompileSourceFilesMethod a method of the form <code>int x(String[] args)</code>. It
+ * should return <code>0</code> if compilation is successful and any non-zero value
+ * otherwise. <b>jmake</b> passes it a list of the <code>.java</code> files to
+ * recompile in the form of canonical full path file names.
+ *
+ * @throws PublicExceptions.NoActionRequestedException if <b>jmake</b> was not requested to do any real work;
+ * @throws PublicExceptions.InvalidCmdOptionException if invalid command line option was detected;
+ * @throws PublicExceptions.PDBCorruptedException if project database is corrupted;
+ * @throws PublicExceptions.CommandFileReadException if there was error reading a command file;
+ * @throws PublicExceptions.CompilerInteractionException if there was a problem initializing or calling the compiler,
+ * or compilation errors were detected;
+ * @throws PublicExceptions.ClassFileParseException if there was error parsing a class file;
+ * @throws PublicExceptions.ClassNameMismatchException if there is a mismatch between the deduced and the actual class name;
+ * @throws PublicExceptions.InvalidSourceFileExtensionException if a specified source file has an invalid extension (not .java);
+ * @throws PublicExceptions.JarDependsOnSourceException if a class in a <code>JAR</code> is found dependent on a class with the .java source;
+ * @throws PublicExceptions.DoubleEntryException if more than one entry for the same class is found in the project
+ * @throws PublicExceptions.InternalException if an internal problem that should never happen was detected.
+ * @throws FileNotFoundException if a <code>.java</code> or a <code>.class</code> file was not found;
+ * @throws IOException if there was an I/O problem of any kind;
+ */
+ public void mainProgrammaticControlled(String addedJavaFileNames[], String removedJavaFileNames[], String updatedJavaFileNames[],
+ String destDirName, String pdbFileName,
+ Object externalApp, Method externalCompileSourceFilesMethod) throws
+ PublicExceptions.NoActionRequestedException,
+ PublicExceptions.InvalidCmdOptionException,
+ PublicExceptions.PDBCorruptedException,
+ PublicExceptions.CommandFileReadException,
+ PublicExceptions.CompilerInteractionException,
+ PublicExceptions.ClassFileParseException,
+ PublicExceptions.ClassNameMismatchException,
+ PublicExceptions.InvalidSourceFileExtensionException,
+ PublicExceptions.JarDependsOnSourceException,
+ PublicExceptions.DoubleEntryException,
+ PublicExceptions.InternalException,
+ FileNotFoundException,
+ IOException {
+
+ controlledExecution = true;
+ this.pdbFileName = pdbFileName;
+ this.destDir = destDirName;
+ this.addedJavaFileNames = addedJavaFileNames;
+ this.removedJavaFileNames = removedJavaFileNames;
+ this.updatedJavaFileNames = updatedJavaFileNames;
+ this.externalApp = externalApp;
+ this.externalCompileSourceFilesMethod = externalCompileSourceFilesMethod;
+
+ mainProgrammatic(null);
+ }
+
+ /**
+ * Main entrypoint for applications such as IDEs, that themselves keep track of updated/added/removed sources,
+ * want to have full control over compilations that <b>jmake</b> invokes, and do not want to handle exceptions
+ * that it may throw. Error codes returned are the same as <code>mainExternal(String[])</code> returns.
+ *
+ * @param addedJavaFileNames names of <code>.java</code> files just added to the project
+ * @param removedJavaFileNames names of <code>.java</code> files just removed from the project
+ * @param updatedJavaFileNames names of updated project <code>.java</code> files
+ * @param destDirName name of the destination directory (<b>jmake</b> will look up binary classes
+ * in there, it should be the same as the one used by the Java compiler method).
+ * If <code>null</code> is passed, classes will be looked up in the same directories
+ * as their sources, in agreement with the default Java compiler behaviour.
+ * @param pdbFileName project database file name (if <code>null</code> is passed,
+ * a file with the default name placed in the current directory will be used).
+ * @param externalApp an object on which to invoke <code>externalCompileSourceFilesMethod</code> method.
+ * @param externalCompileSourceFilesMethod a method of the form <code>int x(String[] args)</code>. It
+ * should return <code>0</code> if compilation is successful and any non-zero value
+ * otherwise. <b>jmake</b> passes it a list of the <code>.java</code> files to
+ * recompile in the form of canonical full path file names.
+ *
+ * @see #mainExternal(String[])
+ */
+ public int mainExternalControlled(String addedJavaFileNames[], String removedJavaFileNames[], String updatedJavaFileNames[],
+ String destDirName, String pdbFileName,
+ Object externalApp, Method externalCompileSourceFilesMethod) {
+ controlledExecution = true;
+ this.pdbFileName = pdbFileName;
+ this.destDir = destDirName;
+ this.addedJavaFileNames = addedJavaFileNames;
+ this.removedJavaFileNames = removedJavaFileNames;
+ this.updatedJavaFileNames = updatedJavaFileNames;
+ this.externalApp = externalApp;
+ this.externalCompileSourceFilesMethod = externalCompileSourceFilesMethod;
+
+ return mainExternal(null);
+ }
+
+ /**
+ * Main entrypoint for the standalone <b>jmake</b> application. This method calls does little but calling
+ * <code>mainExternal</code>, and its execution always completes with <code>System.exit(code)</code>,
+ * where <code>code</code> is the value returned by <code>mainExternal</code>.
+ *
+ * @see #mainExternal(String[])
+ * @see #mainProgrammatic(String[])
+ *
+ * @param args command line arguments passed to <b>jmake</b>
+ */
+ public static void main(String args[]) {
+ Utils.startTiming(Utils.TIMING_TOTAL);
+
+ Main m = new Main();
+ int exitCode = m.mainExternal(args);
+
+ Utils.stopAndPrintTiming("Total", Utils.TIMING_TOTAL);
+ if ( exitCode != 0 ) {
+ System.exit(exitCode);
+ }
+ }
+
+ /**
+ * Customize the output of <b>jmake</b>.
+ *
+ * @see #setOutputStreams(PrintStream, PrintStream, PrintStream)
+ *
+ * @param printInfoMessages specify whether to print information messages
+ * @param printWarningMessages specify whether to print warning messages
+ * @param printErrorMessages specify whether to print error messages
+ */
+ public static void customizeOutput(boolean printInfoMessages,
+ boolean printWarningMessages,
+ boolean printErrorMessages) {
+ Utils.customizeOutput(printInfoMessages, printWarningMessages, printErrorMessages);
+ }
+
+ /**
+ * Set the class path to be used by the compiler, and also by the dependency checker for the purposes of
+ * superclass/superinterface change tracking. For the compiler, this class path will be merged with the
+ * project class path (set via setProjectClassPath(String)). Other than that, its value will be used only to
+ * look up superclasses/superinterfaces of project classes. Note that non-project superclasses and
+ * superinterfaces are first looked up at the boot class path, then on the extension class path, and then
+ * on this class path.
+ *
+ * @see #setProjectClassPath(String)
+ * @see #setBootClassPath(String)
+ * @see #setExtDirs(String)
+ *
+ * @param classPath the value of the class path, in the usual format (i.e. entries that are directories
+ * or JARs, separated by colon or semicolon depending on the platform).
+ *
+ * @throws PublicExceptions.InvalidCmdOptionException if invalid class path value is specified.
+ */
+ public static void setClassPath(String classPath) throws PublicExceptions.InvalidCmdOptionException {
+ ClassPath.setClassPath(classPath);
+ }
+
+ /**
+ * Set the class path to be used by the compiler, and also by the dependency checker for the purposes of
+ * superclass/superinterface change tracking and sourceless class dependency checking. For the compiler,
+ * and also in order to look up superclasses/superinterfaces of project classes, this class path will be
+ * merged with the standard class path (set via setClassPath(String)). But in addition, all binary classes
+ * that are on this class path are stored in the project database and checked for updates every time jmake
+ * is invoked. Any changes to these classes trigger the standard dependency checking procedure. However,
+ * dependent classes are looked up only among the "normal" project classes, i.e. those that have sources.
+ * Therefore sourceless classes are assumed to always be mutually consistent.
+ *
+ * Currently only JAR files can be present on this class path.
+ *
+ * @see #setClassPath(String)
+ *
+ * @param projectClassPath the value of the class path, in the usual format (i.e. entries that are directories
+ * or JARs, separated by colon or semicolon depending on the platform).
+ *
+ * @throws PublicExceptions.InvalidCmdOptionException if invalid class path value is specified.
+ */
+ public static void setProjectClassPath(String projectClassPath) throws PublicExceptions.InvalidCmdOptionException {
+ ClassPath.setProjectClassPath(projectClassPath);
+ }
+
+ /**
+ * Set the boot class path to be used by the compiler (-bootclasspath option) and also by the dependency
+ * checker (by default, the value of "sun.boot.class.path" property is used).
+ *
+ * @see #setClassPath(String)
+ *
+ * @param classPath the value of the boot class path, in the usual format (i.e. entries that are directories
+ * or JARs, separated by colon or semicolon depending on the platform).
+ *
+ * @throws PublicExceptions.InvalidCmdOptionException if invalid class path value is specified.
+ */
+ public static void setBootClassPath(String classPath) throws PublicExceptions.InvalidCmdOptionException {
+ ClassPath.setBootClassPath(classPath);
+ }
+
+ /**
+ * Set the extensions location to be used by the compiler (-extdirs option) and also by the dependency
+ * checker (by default, the value of "java.ext.dirs" property is used).
+ *
+ * @see #setClassPath(String)
+ *
+ * @param dirs the value of extension directories, in the usual format (one or more directory names
+ * separated by colon or semicolon depending on the platform).
+ *
+ * @throws PublicExceptions.InvalidCmdOptionException if invalid class path value is specified.
+ */
+ public static void setExtDirs(String dirs) throws PublicExceptions.InvalidCmdOptionException {
+ ClassPath.setExtDirs(dirs);
+ }
+
+ /**
+ * Set the virtual path used to find both source and class files that are part of the project
+ * but are not in the local directory.
+ *
+ * @see #setClassPath(String)
+ *
+ * @param dirs the value of extension directories, in the usual format (one or more directory names
+ * separated by colon or semicolon depending on the platform).
+ *
+ * @throws PublicExceptions.InvalidCmdOptionException if invalid path value is specified.
+ */
+ public static void setVirtualPath(String dirs) throws PublicExceptions.InvalidCmdOptionException {
+ ClassPath.setVirtualPath(dirs);
+ }
+
+ /** Produce no warning or error message upon a dependent <code>JAR</code> detection. */
+ public static final int DEPJAR_NOWARNORERROR = 0;
+ /** Produce a warning upon a dependent <code>JAR</code> detection. */
+ public static final int DEPJAR_WARNING = 1;
+ /** Produce an error message (throw an exception) upon a dependent <code>JAR</code> detection. */
+ public static final int DEPJAR_ERROR = 2;
+
+ /**
+ * Set the response of <b>jmake</b> in case a dependence of a class located in a <code>JAR</code> file on a
+ * class with a <code>.java</code> source is detected (such dependencies are highly discouraged, since it is not
+ * possible to recompile a class in the <code>JAR</code> that has no source).
+ *
+ * @param code response type: DEPJAR_NOWARNORERROR, DEPJAR_WARNING (default behaviour) or DEPJAR_ERROR.
+ */
+ public void setResponseOnDependentJar(int code) {
+ switch (code) {
+ case DEPJAR_NOWARNORERROR:
+ noWarnOnDependentJar = true;
+ failOnDependentJar = false;
+ break;
+ case DEPJAR_WARNING:
+ noWarnOnDependentJar = false;
+ failOnDependentJar = false;
+ break;
+ case DEPJAR_ERROR:
+ noWarnOnDependentJar = false;
+ failOnDependentJar = true;
+ break;
+ }
+ }
+
+ /**
+ * Return the names of all classes that <b>jmake</b>, on this invocation, found updated - either because
+ * <b>jmake</b> itself recompiled them or because they were updated independently (their timestamp/checksum
+ * found different from those contained in the project database).
+ */
+ public String[] getUpdatedClasses() {
+ return pcdm.getAllUpdatedClassesAsStringArray();
+ }
+
+ /**
+ * Set the output print streams to be used by <b>jmake</b>.
+ *
+ * @see #customizeOutput(boolean, boolean, boolean)
+ *
+ * @param out print stream to be used for information ("logging") messages that <b>jmake</b> emits
+ * @param warn print stream to be used for warning messages
+ * @param err print stream to be used for error messages
+ */
+ public static void setOutputStreams(PrintStream out, PrintStream warn, PrintStream err) {
+ Utils.setOutputStreams(out, warn, err);
+ }
+
+ /** Get the version of this copy of <b>jmake</b> */
+ public static String getVersion() {
+ return VERSION;
+ }
+ private static final String ERR_IS_INVALID_OPTION =
+ " is an invalid option or argument.";
+ private static final String ERR_NO_TWO_COMPILER_OPTIONS =
+ "You may not specify both compiler class and compiler executable application";
+ private static final String ERR_SHOULD_BE_EXPLICIT =
+ " compiler option should be specified directly as a jmake option";
+
+ private static void bailOut(String s) {
+ throw new PrivateException(new PublicExceptions.InvalidCmdOptionException("jmake: " + s + "\nRun \"jmake -h\" for help."));
+ }
+
+ private static void optRequiresArg(String s) {
+ bailOut("the " + s + " option requires an argument.");
+ }
+
+ private static void printUsage() {
+ Utils.printInfoMessage("Usage: jmake <options> <.java files> <@files>");
+ Utils.printInfoMessage("where possible options include:");
+ Utils.printInfoMessage(" -h, -help print this help message");
+ Utils.printInfoMessage(" -version print the product version number");
+ Utils.printInfoMessage(" -pdb <file name> specify non-default project database file");
+ Utils.printInfoMessage(" -pdb-text-format if specified, pdb file is stored in text format");
+ Utils.printInfoMessage(" -d <directory> specify where to place generated class files");
+ Utils.printInfoMessage(" -classpath <path> specify where to find user class files");
+ Utils.printInfoMessage(" -projclasspath <path> specify where to find sourceless project classes");
+ Utils.printInfoMessage(" (currently only JARs are allowed on this path)");
+ Utils.printInfoMessage(" -C<option> specify an option to be passed to the Java compiler");
+ Utils.printInfoMessage(" (this option's arguments should also be preceded by -C)");
+ Utils.printInfoMessage(" -jcpath <path> specify the class path for a non-default Java compiler");
+ Utils.printInfoMessage(" (default is <JAVAHOME>/lib/tools.jar)");
+ Utils.printInfoMessage(" -jcmainclass <class> specify the main class for a non-default Java compiler");
+ Utils.printInfoMessage(" (default is com.sun.tools.javac.Main)");
+ Utils.printInfoMessage(" -jcmethod <method> specify the method to call in the Java compiler class");
+ Utils.printInfoMessage(" (default is \"compile(String args[])\")");
+ Utils.printInfoMessage(" -jcexec <file name> specify a binary non-default Java compiler application");
+ Utils.printInfoMessage(" -failondependentjar fail if a class on projectclasspath depends on a class");
+ Utils.printInfoMessage(" with .java source (by default, a warning is issued)");
+ Utils.printInfoMessage(" -nowarnondependentjar no warning or error if a class on projectclasspath");
+ Utils.printInfoMessage(" depends on a class with a .java source (use with care)");
+ Utils.printInfoMessage(" -warnlimit <number> specify the maximum number of warnings (20 by default)");
+ Utils.printInfoMessage(" -bootclasspath <path> override location of bootstrap class files");
+ Utils.printInfoMessage(" -extdirs <dirs> override location of installed extensions");
+ Utils.printInfoMessage(" -vpath <dirs> a list of directories to search for Java and class files similar to GNUMake's VPATH");
+ Utils.printInfoMessage(" -depfile <path> a file generated by the compiler containing additional java->class mappings");
+ Utils.printInfoMessage("");
+ Utils.printInfoMessage("Examples:");
+ Utils.printInfoMessage(" jmake -d classes -classpath .;mylib.jar X.java Y.java Z.java");
+ Utils.printInfoMessage(" jmake -pdb myproject.pdb -jcexec c:\\java\\jikes\\jikes.exe @myproject.src");
+ }
+}
diff --git a/third_party/jmake/src/org/pantsbuild/jmake/PCDContainer.java b/third_party/jmake/src/org/pantsbuild/jmake/PCDContainer.java
new file mode 100644
index 0000000..b02cfa4
--- /dev/null
+++ b/third_party/jmake/src/org/pantsbuild/jmake/PCDContainer.java
@@ -0,0 +1,64 @@
+/* Copyright (c) 2002-2008 Sun Microsystems, Inc. All rights reserved
+ *
+ * This program is distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+package org.pantsbuild.jmake;
+
+import java.io.File;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * This class is a persistent container for the Project Class Directory, that can
+ * read and write itself from/to disk.
+ *
+ * @author Misha Dmitriev
+ * 12 November 2001
+ */
+public class PCDContainer {
+
+ /** The data structure (currently {@link LinkedHashMap}) for PCD, that maps class name to
+ record containing information about the class */
+ Map<String,PCDEntry> pcd;
+ String storeName;
+ boolean textFormat;
+
+ private PCDContainer(Map<String,PCDEntry> pcd, String storeName, boolean textFormat) {
+ this.storeName = storeName;
+ this.pcd = pcd;
+ this.textFormat = textFormat;
+ }
+
+ public static PCDContainer load(String storeName, boolean textFormat) {
+ if (storeName == null) {
+ storeName = Main.DEFAULT_STORE_NAME;
+ }
+ File storeFile = Utils.checkFileForName(storeName);
+ if (storeFile != null) {
+ Utils.printInfoMessageNoEOL("Opening project database... ");
+ Map<String,PCDEntry> pcd;
+ if (textFormat) {
+ pcd = new TextProjectDatabaseReader().readProjectDatabaseFromFile(storeFile);
+ } else {
+ pcd = new BinaryProjectDatabaseReader().readProjectDatabaseFromFile(storeFile);
+ }
+ PCDContainer pcdc = new PCDContainer(pcd, storeName, textFormat);
+ Utils.printInfoMessage("Done.");
+ return pcdc;
+ }
+ return new PCDContainer(null, storeName, textFormat);
+ }
+
+ public void save() {
+ Utils.printInfoMessageNoEOL("Writing project database... ");
+ File outfile = new File(storeName);
+ if (textFormat) {
+ new TextProjectDatabaseWriter().writeProjectDatabaseToFile(outfile, pcd);
+ } else {
+ new BinaryProjectDatabaseWriter().writeProjectDatabaseToFile(outfile, pcd);
+ }
+ Utils.printInfoMessage("Done.");
+ }
+}
diff --git a/third_party/jmake/src/org/pantsbuild/jmake/PCDEntry.java b/third_party/jmake/src/org/pantsbuild/jmake/PCDEntry.java
new file mode 100644
index 0000000..72fb3a4
--- /dev/null
+++ b/third_party/jmake/src/org/pantsbuild/jmake/PCDEntry.java
@@ -0,0 +1,111 @@
+/* Copyright (c) 2002-2008 Sun Microsystems, Inc. All rights reserved
+ *
+ * This program is distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+package org.pantsbuild.jmake;
+
+import java.io.File;
+
+/**
+ * An instance of this class represents an entry in the Project Class Directory.
+ *
+ * @author Misha Dmitriev
+ * 29 March 2002
+ */
+public class PCDEntry {
+ // Class versions compare results
+
+ static final int CV_UNCHECKED = 0;
+ static final int CV_COMPATIBLE = 1;
+ static final int CV_INCOMPATIBLE = 2;
+ static final int CV_DELETED = 3;
+ static final int CV_NEW = 4;
+ static final int CV_NEWER_FOUND_NEARER = 5;
+ String className; // Dots are replaced with slashes for convenience
+ transient String classFileFullPath;
+ String javaFileFullPath;
+ long oldClassFileLastModified;
+ transient long newClassFileLastModified;
+ long oldClassFileFingerprint;
+ transient long newClassFileFingerprint;
+ ClassInfo oldClassInfo;
+ transient ClassInfo newClassInfo;
+ transient int checkResult; // Reflects the result of class version comparison
+ transient boolean checked; // Mark entries for classes that have been checked and found existing.
+ // It helps to detect double entries for the same class in the project file list,
+ // and also not to confuse them with the case when a .java source for a class is moved.
+
+ /** This constructor is called to initialize a record for a class that has just been added to the project. */
+ public PCDEntry(String className,
+ String javaFileFullPath,
+ String classFileFullPath,
+ long classFileLastModified,
+ long classFileFingerprint,
+ ClassInfo classInfo) {
+ this.className = className;
+ this.classFileFullPath = classFileFullPath;
+ this.javaFileFullPath = javaFileFullPath;
+ this.oldClassFileLastModified = this.newClassFileLastModified =
+ classFileLastModified;
+ this.oldClassFileFingerprint = this.newClassFileFingerprint =
+ classFileFingerprint;
+ this.newClassInfo = classInfo;
+ checked = true;
+ }
+
+ /**
+ * This constructor is called to initialize a record for a class that
+ * exists at least in the previous version of the project.
+ */
+ public PCDEntry(String className,
+ String javaFileFullPath,
+ long classFileLastModified,
+ long classFileFingerprint,
+ ClassInfo classInfo) {
+ this.className = className;
+ this.javaFileFullPath = javaFileFullPath;
+ this.oldClassFileLastModified = classFileLastModified;
+ this.oldClassFileFingerprint = classFileFingerprint;
+ this.oldClassInfo = classInfo;
+ }
+
+ // Debugging
+ public String toString() {
+ return "className = " + className +
+ "; classFileFullPath = " + classFileFullPath +
+ "; javaFileFullPath = " + javaFileFullPath;
+ }
+
+ /**
+ * Returns the name of the class that corresponds to the file name, i.e. the public class
+ */
+ private String getExpectedClassName() {
+ File path = new File(javaFileFullPath);
+ int index = -1;
+ do {
+ index = className.indexOf('/', index + 1);
+ path = path.getParentFile();
+ } while (index != -1);
+ String pathString = path.toString();
+ if (!pathString.endsWith("/"))
+ pathString += "/";
+ // It is assumed that the javaFileFillPath ends with .java
+ int javaPathWithoutSuffix = javaFileFullPath.length() - 5;
+ return javaFileFullPath.substring(pathString.length(),
+ javaPathWithoutSuffix);
+ }
+
+ /**
+ * A class that neither has the same name as the java file, nor an inner class, is
+ * package-private.
+ */
+ public boolean isPackagePrivateClass() {
+ String expectedClassName = getExpectedClassName();
+
+ return !(className.equals(expectedClassName)
+ || (className.startsWith(expectedClassName)
+ && className.charAt(expectedClassName.length()) == '$'));
+ }
+}
diff --git a/third_party/jmake/src/org/pantsbuild/jmake/PCDManager.java b/third_party/jmake/src/org/pantsbuild/jmake/PCDManager.java
new file mode 100644
index 0000000..5ff3cd1
--- /dev/null
+++ b/third_party/jmake/src/org/pantsbuild/jmake/PCDManager.java
@@ -0,0 +1,1603 @@
+/* Copyright (c) 2002-2008 Sun Microsystems, Inc. All rights reserved
+ *
+ * This program is distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+package org.pantsbuild.jmake;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.zip.Adler32;
+
+/**
+ * This class implements management of the Project Class Directory, automatic tracking
+ * of changes and recompilation of .java sources for a project.
+ *
+ * @author Misha Dmitriev
+ * 23 January 2003
+ */
+public class PCDManager {
+
+ private PCDContainer pcdc;
+ private Map<String,PCDEntry> pcd; // Maps project class names to PCDEntries
+ private String projectJavaAndJarFilesArray[];
+ private String addedJavaAndJarFilesArray[], removedJavaAndJarFilesArray[], updatedJavaAndJarFilesArray[];
+ private List<String> newJavaFiles;
+ private Set<String> updatedJavaFiles;
+ private Set<String> recompiledJavaFiles;
+ private Set<String> updatedClasses; // This set is emptied on every new internal jmake iteration...
+ private Set<String> allUpdatedClasses; // whereas in this one the names of all updated classes found during this jmake invocation are stored.
+ private Set<String> updatedAndCheckedClasses;
+ private Set<String> deletedClasses;
+ private Set<String> updatedJarFiles;
+ private Set<String> stableJarFiles;
+ private Set<String> newJarFiles;
+ private Set<String> deletedJarFiles;
+ /* Dependencies from the dependencyFile, if any */
+ private Map<String, List<String>> extraDependencies;
+
+ private String destDir;
+ private boolean destDirSpecified;
+ private List<String> javacAddArgs;
+ private Class<?> compilerClass;
+ private Method compileMethod;
+ private String jcExecApp;
+ private Object externalApp;
+ private Method externalCompileSourceFilesMethod;
+ private Adler32 checkSum;
+ private CompatibilityChecker cv;
+ private ClassFileReader cfr;
+ private boolean newProject = false;
+ private String dependencyFile = null;
+ private static boolean backSlashFileSeparator = File.separatorChar != '/';
+
+ /**** Interface to the class ****/
+ /**
+ * Either projectJavaAndJarFilesArray != null and added.. == removed.. == updatedJavaAndJarFilesArray == null,
+ * or projectJavaAndJarFilesArray == null and one or more of others != null.
+ * When PCDManager is called from Main, this is guaranteed, since separate entrypoint functions initialize
+ * either one or another of the above argument groups, but never both.
+ */
+ public PCDManager(PCDContainer pcdc,
+ String projectJavaAndJarFilesArray[],
+ String addedJavaAndJarFilesArray[],
+ String removedJavaAndJarFilesArray[],
+ String updatedJavaAndJarFilesArray[],
+ String in_destDir,
+ List<String> javacAddArgs,
+ boolean failOnDependentJar,
+ boolean noWarnOnDependentJar,
+ String dependencyFile) {
+ this.pcdc = pcdc;
+ if (pcdc.pcd == null) {
+ pcd = new LinkedHashMap<String,PCDEntry>();
+ pcdc.pcd = pcd;
+ newProject = true;
+ } else {
+ pcd = pcdc.pcd;
+ }
+
+ this.projectJavaAndJarFilesArray = projectJavaAndJarFilesArray;
+ this.addedJavaAndJarFilesArray = addedJavaAndJarFilesArray;
+ this.removedJavaAndJarFilesArray = removedJavaAndJarFilesArray;
+ this.updatedJavaAndJarFilesArray = updatedJavaAndJarFilesArray;
+ this.dependencyFile = dependencyFile;
+ newJavaFiles = new ArrayList<String>();
+ updatedJavaFiles = new LinkedHashSet<String>();
+ recompiledJavaFiles = new LinkedHashSet<String>();
+ updatedAndCheckedClasses = new LinkedHashSet<String>();
+ deletedClasses = new LinkedHashSet<String>();
+ allUpdatedClasses = new LinkedHashSet<String>();
+
+ updatedJarFiles = new LinkedHashSet<String>();
+ stableJarFiles = new LinkedHashSet<String>();
+ newJarFiles = new LinkedHashSet<String>();
+ deletedJarFiles = new LinkedHashSet<String>();
+
+ initializeDestDir(in_destDir);
+ this.javacAddArgs = javacAddArgs;
+
+ checkSum = new Adler32();
+
+ cv = new CompatibilityChecker(this, failOnDependentJar, noWarnOnDependentJar);
+ cfr = new ClassFileReader();
+ }
+
+ public Collection<PCDEntry> entries() {
+ return pcd.values();
+ }
+
+ public ClassFileReader getClassFileReader() {
+ return cfr;
+ }
+
+ public ClassInfo getClassInfoForName(int verCode, String className) {
+ PCDEntry pcde = pcd.get(className);
+ if (pcde != null) {
+ return getClassInfoForPCDEntry(verCode, pcde);
+ } else {
+ return null;
+ }
+ }
+
+ public boolean isProjectClass(int verCode, String className) {
+ if (verCode == ClassInfo.VER_OLD) {
+ return pcd.containsKey(className);
+ } else {
+ PCDEntry pcde = pcd.get(className);
+ return (pcde != null && pcde.checkResult != PCDEntry.CV_DELETED);
+ }
+ }
+
+ /**
+ * Get an instance of ClassInfo (load a class file if necessary) for the given version (old or new) of
+ * the class determined by pcde. For an old class version, always returns a non-null result; but for a new
+ * version, null is returned if class file is not found. In most of the current uses of this method null result
+ * is not checked, because it's either called for an old version or it is already known that the .class file
+ * should be present; nevertheless, beware!
+ */
+ public ClassInfo getClassInfoForPCDEntry(int verCode, PCDEntry pcde) {
+ if (verCode == ClassInfo.VER_OLD) {
+ return pcde.oldClassInfo;
+ }
+
+ ClassInfo res = pcde.newClassInfo;
+ if (res == null) {
+ byte classFileBytes[];
+ String classFileFullPath = null;
+ if (pcde.javaFileFullPath.endsWith(".java")) {
+ File classFile = Utils.checkFileForName(pcde.classFileFullPath);
+ if (classFile == null) {
+ return null; // Class file not found.
+ }
+ classFileBytes = Utils.readFileIntoBuffer(classFile);
+ classFileFullPath = pcde.classFileFullPath;
+ } else {
+ try {
+ JarFile jarFile = new JarFile(pcde.javaFileFullPath);
+ JarEntry jarEntry =
+ jarFile.getJarEntry(pcde.className + ".class");
+ if (jarEntry == null) {
+ return null;
+ }
+ classFileBytes =
+ Utils.readZipEntryIntoBuffer(jarFile, jarEntry);
+ } catch (IOException ex) {
+ throw new PrivateException(ex);
+ }
+ }
+ res =
+ new ClassInfo(classFileBytes, verCode, this, classFileFullPath);
+ pcde.newClassInfo = res;
+ }
+ return res;
+ }
+
+ /**
+ * Returns null if class is compileable (has a .java source) and not recompiled yet, "" if
+ * class has already been recompiled or has been deleted from project, and the class's .jar
+ * name if class comes from a jar, hence is uncompileable.
+ */
+ public String classAlreadyRecompiledOrUncompileable(String className) {
+ PCDEntry pcde = pcd.get(className);
+ if (pcde == null) {
+ //!!!
+ for (String keyName : pcd.keySet()) {
+ PCDEntry entry = pcd.get(keyName);
+ if (entry.className.equals(className)) {
+ System.out.println("ERROR: inconsistent entry: key = " +
+ keyName + ", name in entry = " + entry.className);
+ }
+ }
+ //!!!
+ throw internalException(className + " not in project when it should be");
+ }
+ if (pcde.checkResult == PCDEntry.CV_DELETED) {
+ return "";
+ }
+ if (pcde.javaFileFullPath.endsWith(".jar")) {
+ return pcde.javaFileFullPath;
+ } else {
+ return (recompiledJavaFiles.contains(pcde.javaFileFullPath) ? "" : null);
+ }
+ }
+
+ /**
+ * Compiler initialization depends on compiler type specified.
+ * If jcExecApp != null, i.e. an external executable compiler application is used, and nothing has to be done.
+ * If externalApp != null, that is, jmake is called by an external application such as Ant, which
+ * manages compilation in its own way, and also nothing has to be done.
+ * Otherwise, load the compiler class and method (either specified through jcPath, jcMainClass and jcMethod,
+ * or the default one.
+ */
+ public void initializeCompiler(String jcExecApp,
+ String jcPath, String jcMainClass, String jcMethod,
+ Object externalApp, Method externalCompileSourceFilesMethod) {
+ ClassPath.initializeAllClassPaths();
+
+ if (externalApp != null) {
+ this.externalApp = externalApp;
+ this.externalCompileSourceFilesMethod =
+ externalCompileSourceFilesMethod;
+ return;
+ }
+ if (jcExecApp != null) {
+ this.jcExecApp = jcExecApp;
+ return;
+ }
+
+ if (jcPath == null) {
+ String javaHome = System.getProperty("java.home");
+ // In my tests it ends with '/jre'. Or it could be ending with '/bin' as well? Let's assume it can be both and delete
+ // this latter directory.
+ if (javaHome.endsWith(File.separator + "jre") || javaHome.endsWith(File.separator + "bin")) {
+ javaHome = javaHome.substring(0, javaHome.length() - 4);
+ }
+ jcPath = javaHome + "/lib/tools.jar";
+ }
+ ClassLoader compilerLoader;
+ try {
+ compilerLoader = ClassPath.getClassLoaderForPath(jcPath);
+ } catch (Exception ex) {
+ throw compilerInteractionException("error opening compiler path", ex, 0);
+ }
+
+ if (jcMainClass == null) {
+ jcMainClass = "com.sun.tools.javac.Main";
+ }
+ if (jcMethod == null) {
+ jcMethod = "compile";
+ }
+
+ try {
+ compilerClass = compilerLoader.loadClass(jcMainClass);
+ } catch (ClassNotFoundException e) {
+ throw compilerInteractionException("error loading compiler main class " + jcMainClass, e, 0);
+ }
+
+ Class<?>[] args = new Class<?>[]{String[].class};
+ try {
+ compileMethod = compilerClass.getMethod(jcMethod, args);
+ } catch (Exception e) {
+ throw compilerInteractionException("error getting method com.sun.tools.javac.Main.compile(String args[])", e, 0);
+ }
+ }
+
+ /** Main entrypoint for this class */
+ public void run() {
+ Utils.startTiming(Utils.TIMING_SYNCHRO);
+ synchronizeProjectFilesAndPCD();
+ Utils.stopAndPrintTiming("Synchro", Utils.TIMING_SYNCHRO);
+ Utils.printTiming("of which synchro check file", Utils.TIMING_SYNCHRO_CHECK_JAVA_FILES);
+
+ Utils.startTiming(Utils.TIMING_FIND_UPDATED_JAVA_FILES);
+ findUpdatedJavaAndJarFiles();
+ Utils.stopAndPrintTiming("findUpdatedJavaAndJarFiles", Utils.TIMING_FIND_UPDATED_JAVA_FILES);
+ Utils.printTiming("of which classFileObsoleteOrDeleted", Utils.TIMING_CLASS_FILE_OBSOLETE_OR_DELETED);
+
+ // Let's free some memory
+ projectJavaAndJarFilesArray = null;
+
+ updatedClasses = new LinkedHashSet<String>();
+ dealWithClassesInUpdatedJarFiles();
+
+ int iterNo = 0;
+ int res = 0;
+ while (iterNo == 0 || updatedJavaFiles.size() != 0 || newJavaFiles.size() != 0) {
+ // It may happen that we didn't find any updated or new .java files. However, we still need to enter
+ // this loop because there may be some class files that need compatibility checking. This can happen
+ // either if somebody had recompiled their sources bypassing jmake, or if their checking during the
+ // previous invocation of jmake failed, because their dependent code recompilation failed.
+ if (updatedJavaFiles.size() > 0 || newJavaFiles.size() > 0) {
+ Utils.startTiming(Utils.TIMING_COMPILE);
+ int intermediateRes = recompileUpdatedJavaFiles();
+ Utils.stopAndPrintTiming("Compile", Utils.TIMING_COMPILE);
+ if (intermediateRes != 0) {
+ res = intermediateRes;
+ }
+ }
+
+ Utils.startTiming(Utils.TIMING_PDBUPDATE);
+ // New classes can be added to pdb only if compilation was successful, i.e. the new project version is consistent.
+ if (iterNo++ == 0 && res == 0) {
+ findClassFilesForNewJavaAndJarFiles();
+ findClassFilesForUpdatedJavaFiles();
+ dealWithNestedClassesForUpdatedJavaFiles();
+ }
+ Utils.stopAndPrintTiming("Entering new classes in PDB", Utils.TIMING_PDBUPDATE);
+
+ updatedJavaFiles.clear();
+ newJavaFiles.clear();
+
+ Utils.startTiming(Utils.TIMING_FIND_UPDATED_CLASSES);
+ findUpdatedClasses();
+ Utils.stopAndPrintTiming("Find updated classes", Utils.TIMING_FIND_UPDATED_CLASSES);
+
+ Utils.startTiming(Utils.TIMING_CHECK_UPDATED_CLASSES);
+ checkDeletedClasses();
+ checkUpdatedClasses();
+ Utils.stopAndPrintTiming("Check updated classes", Utils.TIMING_CHECK_UPDATED_CLASSES);
+
+ updatedClasses = new LinkedHashSet<String>();
+ if (ClassPath.getVirtualPath() != null) {
+ if (res != 0)
+ break;
+ }
+ }
+
+ Utils.startTiming(Utils.TIMING_PDBWRITE);
+ updateClassFilesInfoInPCD(res);
+ pcdc.save();
+ Utils.stopAndPrintTiming("PDB write", Utils.TIMING_PDBWRITE);
+
+ if (res != 0) {
+ throw compilerInteractionException("compilation error(s)", null, res);
+ }
+ }
+
+ /**
+ * Find the newly-created class files for existing java files.
+ */
+ private void findClassFilesForUpdatedJavaFiles() {
+ if (dependencyFile == null)
+ return;
+
+ Set<String> allClasses = new HashSet<String>();
+
+ Map<String, List<String>> dependencies = parseDependencyFile();
+ for (String file : updatedJavaFiles) {
+ List<String> myDeps = dependencies.get(file);
+ if (myDeps != null) {
+ PCDEntry parent = getNamedPCDE(file, dependencies);
+ for (String dependency : myDeps) {
+ allClasses.add(dependency);
+ if (pcd.containsKey(dependency))
+ continue;
+ findClassFileOnFilesystem(file, parent, dependency, false);
+ }
+ }
+ }
+ for (Map.Entry<String, PCDEntry> entry : pcd.entrySet()) {
+ String cls = entry.getKey();
+ if (!allClasses.contains(cls)) {
+ PCDEntry pcde = entry.getValue();
+ if (updatedJavaFiles.contains(pcde.javaFileFullPath)) {
+ deletedClasses.add(cls);
+ }
+ }
+ }
+ }
+
+ public String[] getAllUpdatedClassesAsStringArray() {
+ String[] res = new String[allUpdatedClasses.size()];
+ int i = 0;
+ for (String updatedClass : allUpdatedClasses) {
+ res[i++] = updatedClass.replace('/', '.');
+ }
+ return res;
+ }
+
+ /**
+ * Synchronize projectJavaAndJarFilesArray and PCD, i.e. leave only those entries in the PCD which have their
+ * .java (.jar) files in projectJavaAndJarFilesArray. New .java files in projectJavaAndJarFilesArray (i.e. those
+ * for which there are no entries in the PCD yet) are added to newJavaFiles; new .jar files are added to newJarFiles.
+ * Alternatively, just use the supplied arrays of added and deleted .java and .jar files.
+ *
+ * For entries whose .java files are not in the PCD anymore, try to delete .class files. We need to do that before
+ * compilation to avoid the situation when a .java file is removed but compilation succeeds because the .class file
+ * is still there.
+ *
+ * Unfortunately, we also need to delete all class files for non-nested classes whose names differ from their .java
+ * file name, because we can't tell when they've been removed from their .java files -- but it's only safe to do this
+ * for files that originate from java files that we're compiling this round.
+ *
+ * Upon return from this method, all of the .java and .jar files in the PCD are known to exist.
+ */
+ private void synchronizeProjectFilesAndPCD() {
+ if (projectJavaAndJarFilesArray != null) {
+ Set<String> pcdJavaFilesSet = new LinkedHashSet<String>(pcd.size() * 3 / 2);
+ for(PCDEntry entry : entries()) {
+ pcdJavaFilesSet.add(entry.javaFileFullPath);
+ }
+
+ Set<String> canonicalPJF =
+ new LinkedHashSet<String>(projectJavaAndJarFilesArray.length * 3 / 2);
+
+ // Add .java files that are not in PCD to newJavaFiles; add .jar files that are not in PCD to newJarFiles.
+ for (int i = 0; i < projectJavaAndJarFilesArray.length; i++) {
+ String projFileName = projectJavaAndJarFilesArray[i];
+ Utils.startTiming(Utils.TIMING_SYNCHRO_CHECK_TMP);
+ File projFile = Utils.checkFileForName(projFileName);
+ Utils.stopAndAddTiming(Utils.TIMING_SYNCHRO_CHECK_TMP, Utils.TIMING_SYNCHRO_CHECK_JAVA_FILES);
+ if (projFile == null) {
+ throw new PrivateException(new FileNotFoundException("specified source file " + projFileName + " not found."));
+ }
+ // The main reason for using getAbsolutePath() instead of more reliable getCanonicalPath() is the fact that
+ // sometimes users may name the actual files containing Java code in some custom way, and give javac/jmake
+ // symbolic links to these files (that have correct .java names) instead. getCanonicalPath(), however, returns the
+ // real (i.e. user custom) file name, which will confuse our test below and then javac.
+ String absoluteProjFileName = projFile.getAbsolutePath();
+ // On Windows, make sure the drive letter is always in lower case
+ if (backSlashFileSeparator) {
+ absoluteProjFileName =
+ Utils.convertDriveLetterToLowerCase(absoluteProjFileName);
+ }
+ canonicalPJF.add(absoluteProjFileName);
+ if (!pcdJavaFilesSet.contains(absoluteProjFileName)) {
+ if (absoluteProjFileName.endsWith(".java")) {
+ newJavaFiles.add(absoluteProjFileName);
+ } else if (absoluteProjFileName.endsWith(".jar")) {
+ newJarFiles.add(absoluteProjFileName);
+ } else {
+ throw new PrivateException(new PublicExceptions.InvalidSourceFileExtensionException("specified source file " + projFileName + " has an invalid extension (not .java or .jar)."));
+ }
+ }
+ }
+
+ // Find the entries containing .java or .jar files that are not in project anymore
+ for (Entry<String, PCDEntry> entry : pcd.entrySet()) {
+ String key = entry.getKey();
+ PCDEntry e = entry.getValue();
+ e.oldClassInfo.restorePCDM(this);
+ if (canonicalPJF.contains(e.javaFileFullPath)) {
+ if (e.isPackagePrivateClass()) {
+ initializeClassFileFullPath(e);
+ new File(e.classFileFullPath).delete();
+ }
+ } else {
+ if (ClassPath.getVirtualPath() == null) {
+ deletedClasses.add(key);
+ } else {
+ // Okay, not found locally, but virtual path was defined, so try it now....
+ if ( (e.oldClassFileFingerprint == projectJavaAndJarFilesArray.length &&
+ newJavaFiles.size() == 0) ||
+ Utils.checkFileForName(e.javaFileFullPath) != null)
+ {
+ e.checkResult = PCDEntry.CV_NEWER_FOUND_NEARER;
+ e.oldClassFileFingerprint = projectJavaAndJarFilesArray.length;
+ }
+ else
+ {
+ String classFound = null;
+ String sourceFound = null;
+ // Find source and class file via virtual path
+ String path = ClassPath.getVirtualPath();
+ // TODO(Eric Ayers): IntelliJ static analysis shows several useless
+ // expressions that make this loop a no-op.
+ for (StringTokenizer st = new StringTokenizer(path, File.pathSeparator);
+ !(classFound != null && sourceFound != null) && st.hasMoreTokens();)
+ {
+ String fullPath = st.nextToken()+File.separator+e.className;
+ if (sourceFound != null && new File(fullPath+".java").exists())
+ {
+ sourceFound = fullPath + ".java";
+ }
+ if (classFound != null && new File(fullPath+".class").exists())
+ {
+ classFound = fullPath + ".class";
+ }
+ }
+ // TODO(Eric Ayers): IntelliJ static analysis shows that this expression
+ // is always true.
+ if (classFound == null)
+ {
+ deletedClasses.add(key);
+ if (e.javaFileFullPath.endsWith(".jar"))
+ {
+ deletedJarFiles.add(e.javaFileFullPath);
+ }
+ else
+ {
+ initializeClassFileFullPath(e);
+ (new File(e.classFileFullPath)).delete();
+ }
+ }
+ else if (sourceFound != null)
+ {
+ newJavaFiles.add(sourceFound);
+ e.checkResult = PCDEntry.CV_NEWER_FOUND_NEARER;
+ e.oldClassFileFingerprint = projectJavaAndJarFilesArray.length;
+ }
+ else
+ {
+ classFound = classFound.replace('/', File.separatorChar);
+ throw new PrivateException(new FileNotFoundException("deleted class " + classFound + " still exists."));
+ }
+ }
+ }
+ if (e.javaFileFullPath.endsWith(".jar")) {
+ deletedJarFiles.add(e.javaFileFullPath);
+ } else { // Try to delete a class file for the removed project class.
+ initializeClassFileFullPath(e);
+ (new File(e.classFileFullPath)).delete();
+ }
+ }
+ }
+ } else { // projectJavaAndJarFilesArray == null - use supplied arrays of added and removed .java and .jar files
+ if (addedJavaAndJarFilesArray != null) {
+ for (String fileName : addedJavaAndJarFilesArray) {
+ fileName = fileName.intern();
+ if (fileName.endsWith(".java")) {
+ newJavaFiles.add(fileName);
+ } else if (fileName.endsWith(".jar")) {
+ newJarFiles.add(fileName);
+ } else {
+ throw new PrivateException(new PublicExceptions.InvalidSourceFileExtensionException(
+ "specified source file " + fileName + " has an invalid extension (not .java or .jar)."));
+ }
+ }
+ }
+
+ Set<String> removedJavaAndJarFilesSet = null;
+ if (removedJavaAndJarFilesArray != null) {
+ removedJavaAndJarFilesSet = new LinkedHashSet<String>();
+ for (String fileName : removedJavaAndJarFilesArray) {
+ fileName = fileName.intern();
+ removedJavaAndJarFilesSet.add(fileName);
+ if (fileName.endsWith(".jar")) {
+ deletedJarFiles.add(fileName);
+ }
+ }
+ }
+
+ for (Entry<String, PCDEntry> entry : pcd.entrySet()) {
+ String key = entry.getKey();
+ PCDEntry e = entry.getValue();
+ e.oldClassInfo.restorePCDM(this);
+ if (removedJavaAndJarFilesSet != null &&
+ removedJavaAndJarFilesSet.contains(e.javaFileFullPath)) {
+ deletedClasses.add(key);
+ if (!e.javaFileFullPath.endsWith(".jar")) { // Try to delete a class file for the removed project class.
+ initializeClassFileFullPath(e);
+ (new File(e.classFileFullPath)).delete();
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * In the end of run, update the information in the project database for the class files which have
+ * been updated and checked, or deleted. If compilationResult == 0, i.e. all recompilations were
+ * successful, information for new versions of all of the classes is made permanent, and entries
+ * for deleted classes are removed permanently. Otherwise, information is updated only for those
+ * classes whose old and new versions were found source compatible.
+ */
+ private void updateClassFilesInfoInPCD(int compilationResult) {
+ // If the project appears to be inconsistent after changes, make a preliminary pass that will deal with enclosing
+ // classes for deleted nested classes. The problem with them can be as follows: we delete a nested class C$X,
+ // which is still referenced from somewhere. However, C has not changed at all or at least incompatibly, and
+ // thus we update its PCDEntry, which now does not reference C$X. Other parts of jmake require that a nested
+ // class is always referenced from its directly enclusing class, thus to keep the PCD consistent we have to remove
+ // C$X from the PCD. On the next invocation of jmake, C$X is not in the PDB at all, and thus any classes that
+ // may still reference it and have not been updated are not checked => project becomes inconsistent. We could do
+ // better by immediately marking enclosing classes incompatible once we detect that a deleted nested class is
+ // really referenced from somewhere, but the solution below seems to be more robust.
+ if (compilationResult != 0) {
+ for (String className : updatedAndCheckedClasses) {
+ PCDEntry entry = pcd.get(className);
+ if (entry.checkResult == PCDEntry.CV_DELETED &&
+ !"".equals(entry.oldClassInfo.directlyEnclosingClass)) {
+ PCDEntry enclEntry =
+ pcd.get(entry.oldClassInfo.directlyEnclosingClass);
+ enclEntry.checkResult = PCDEntry.CV_INCOMPATIBLE;
+ }
+ }
+ }
+
+ for (String className : updatedAndCheckedClasses) {
+ PCDEntry entry = pcd.get(className);
+ if (entry.checkResult == PCDEntry.CV_UNCHECKED) {
+ continue;
+ }
+ if (ClassPath.getVirtualPath() != null) {
+ if (entry.checkResult == PCDEntry.CV_NEWER_FOUND_NEARER) {
+ continue;
+ }
+ }
+ if (entry.checkResult == PCDEntry.CV_DELETED) {
+ if (compilationResult == 0) {
+ pcd.remove(className); // Only if consistency checking is ok, a deleted class can be safely removed from the PCD
+ }
+ } else if (entry.checkResult == PCDEntry.CV_COMPATIBLE ||
+ entry.checkResult == PCDEntry.CV_NEW ||
+ (entry.checkResult == PCDEntry.CV_INCOMPATIBLE && compilationResult == 0)) {
+ if (entry.newClassInfo == null) { // "Safety net" for the (hopefully unlikely) case we overlooked something before...
+ Utils.printWarningMessage("Warning: internal information inconsistency detected during pdb updating");
+ Utils.printWarningMessage(Utils.REPORT_PROBLEM);
+ Utils.printWarningMessage("Class name: " + className);
+ if (entry.checkResult == PCDEntry.CV_NEW) {
+ pcd.remove(className);
+ } else {
+ continue;
+ }
+ }
+ entry.oldClassFileLastModified = entry.newClassFileLastModified;
+ entry.oldClassFileFingerprint = entry.newClassFileFingerprint;
+ entry.oldClassInfo = entry.newClassInfo;
+ }
+ }
+ }
+
+ /**
+ * Find all .java files on the filesystem, for which the .class file does not exist
+ * or is newer than the .java file. Also find all .jar files for which the timestamp
+ * has changed. Alternatively, just use the supplied array of updated .java/.jar files.
+ */
+ private void findUpdatedJavaAndJarFiles() {
+ boolean projectSpecifiedAsAllSources =
+ projectJavaAndJarFilesArray != null;
+ for (PCDEntry entry : entries()) {
+ if (deletedClasses.contains(entry.className)) {
+ continue;
+ }
+ if (entry.javaFileFullPath.endsWith(".java")) {
+ initializeClassFileFullPath(entry);
+ if (projectSpecifiedAsAllSources) {
+ if (ClassPath.getVirtualPath() != null) {
+ String paths[] = ClassPath.getVirtualPath().split(File.pathSeparator);
+ String tmpClassName = entry.className;
+ tmpClassName = tmpClassName.replaceAll("\\Q$\\E.*$", "");
+ for (int i=0; i<paths.length; i++) {
+ String tmpFilename = paths[i] + File.separator + tmpClassName + ".java";
+ File tmpFile = new File(tmpFilename);
+ if (tmpFile.exists()) {
+ entry.javaFileFullPath = tmpFile.getAbsolutePath();
+ break;
+ }
+ }
+ }
+ Utils.startTiming(Utils.TIMING_CLASS_FILE_OBSOLETE_TMP);
+ if (classFileObsoleteOrDeleted(entry)) {
+ updatedJavaFiles.add(entry.javaFileFullPath);
+ }
+ Utils.stopAndAddTiming(Utils.TIMING_CLASS_FILE_OBSOLETE_TMP, Utils.TIMING_CLASS_FILE_OBSOLETE_OR_DELETED);
+ }
+ entry.checked = true;
+ } else { // Class coming from a .jar file. Mark this entry as checked only if its JAR hasn't changed
+ if (projectJavaAndJarFilesArray != null) {
+ entry.checked = !checkJarFileForUpdate(entry);
+ }
+ }
+ }
+
+ // Lists of updated/added/deleted source files specified instead of a full list of project sources
+ if (!projectSpecifiedAsAllSources && updatedJavaAndJarFilesArray != null) {
+ for (int i = 0; i < updatedJavaAndJarFilesArray.length; i++) {
+ if (updatedJavaAndJarFilesArray[i].endsWith(".java")) {
+ updatedJavaFiles.add(updatedJavaAndJarFilesArray[i]);
+ } else {
+ updatedJarFiles.add(updatedJavaAndJarFilesArray[i]);
+ }
+ }
+ }
+ }
+
+ private boolean classFileObsoleteOrDeleted(PCDEntry entry) {
+ if (ClassPath.getVirtualPath() != null) {
+ File file1 = new File(entry.javaFileFullPath);
+ if (!file1.exists())
+ throw new PrivateException(new FileNotFoundException("specified source file " +
+ entry.javaFileFullPath + " not found."));
+ if (file1.lastModified() < entry.oldClassFileLastModified)
+ {
+ return false;
+ }
+ }
+ File classFile = Utils.checkFileForName(entry.classFileFullPath);
+ if (classFile == null || !classFile.exists()) {
+ return true; // Class file has been deleted
+ }
+ File javaFile = new File(entry.javaFileFullPath); // Guaranteed to exist at this point
+ if (classFile.lastModified() <= javaFile.lastModified()) {
+ return true;
+ }
+ return false;
+ }
+
+ private boolean checkJarFileForUpdate(PCDEntry entry) {
+ String jarFileName = entry.javaFileFullPath;
+ if (stableJarFiles.contains(jarFileName)) {
+ return false;
+ } else if (updatedJarFiles.contains(jarFileName) ||
+ newJarFiles.contains(jarFileName) ||
+ deletedJarFiles.contains(jarFileName)) {
+ return true;
+ } else {
+ File jarFile = new File(jarFileName); // Guaranteed to exist at this point.
+ if (entry.oldClassFileLastModified != jarFile.lastModified()) {
+ updatedJarFiles.add(jarFileName);
+ return true;
+ } else {
+ stableJarFiles.add(jarFileName);
+ return false;
+ }
+ }
+ }
+
+ public int recompileUpdatedJavaFiles() {
+ if (externalApp != null) {
+ return recompileUpdatedJavaFilesUsingExternalMethod();
+ } else {
+ return recompileUpdatedJavaFilesOurselves();
+ }
+ }
+
+ private int recompileUpdatedJavaFilesOurselves() {
+ int filesNo = updatedJavaFiles.size() + newJavaFiles.size();
+ int addArgsNo = javacAddArgs.size();
+ int argsNo = addArgsNo + filesNo + 2;
+ String compilerBootClassPath, compilerExtDirs;
+ if ((compilerBootClassPath = ClassPath.getCompilerBootClassPath()) != null) {
+ argsNo += 2;
+ }
+ if ((compilerExtDirs = ClassPath.getCompilerExtDirs()) != null) {
+ argsNo += 2;
+ }
+ if (jcExecApp != null) {
+ argsNo++;
+ }
+ String args[] = new String[argsNo];
+ int pos = 0;
+ if (jcExecApp != null) {
+ args[pos++] = jcExecApp;
+ }
+ for (int i = 0; i < addArgsNo; i++) {
+ args[pos++] = javacAddArgs.get(i);
+ }
+ args[pos++] = "-classpath";
+ args[pos++] = ClassPath.getCompilerUserClassPath();
+ if (compilerBootClassPath != null) {
+ args[pos++] = "-bootclasspath";
+ args[pos++] = compilerBootClassPath;
+ }
+ if (compilerExtDirs != null) {
+ args[pos++] = "-extdirs";
+ args[pos++] = compilerExtDirs;
+ }
+ if (!newProject) {
+ Utils.printInfoMessage("Recompiling source files:");
+ }
+ for (String javaFileFullPath : updatedJavaFiles) {
+ if (!newProject) {
+ Utils.printInfoMessage(javaFileFullPath);
+ }
+ recompiledJavaFiles.add(args[pos++] = javaFileFullPath);
+ }
+ for (int j = 0; j < newJavaFiles.size(); j++) {
+ String javaFileFullPath = newJavaFiles.get(j);
+ if (!newProject) {
+ Utils.printInfoMessage(javaFileFullPath);
+ }
+ recompiledJavaFiles.add(args[pos++] = javaFileFullPath);
+ }
+
+ if (jcExecApp == null) { // Executing javac or some other compiler within the same JVM
+ Object reflectArgs[] = new Object[1];
+ reflectArgs[0] = args;
+ try {
+ Object dummy = compilerClass.newInstance();
+ Integer res = (Integer) compileMethod.invoke(dummy, reflectArgs);
+ return res.intValue();
+ } catch (Exception e) {
+ throw compilerInteractionException("exception thrown when trying to invoke the compiler method", e, 0);
+ }
+ } else { // Executing an external Java compiler, such as jikes
+ int exitCode = 0;
+ try {
+ Process p = Runtime.getRuntime().exec(args);
+ InputStream pErr = p.getErrorStream();
+ InputStream pOut = p.getInputStream();
+ boolean terminated = false;
+
+ while (!terminated) {
+ try {
+ exitCode = p.exitValue();
+ terminated = true;
+ } catch (IllegalThreadStateException itse) { // Process not yet terminated, wait for some time
+ Utils.ignore(itse);
+ Utils.delay(100);
+ }
+ try {
+ Utils.readAndPrintBytesFromStream(pErr, System.err);
+ Utils.readAndPrintBytesFromStream(pOut, System.out);
+ } catch (IOException ioe1) {
+ throw compilerInteractionException("I/O error when reading the compiler application output", ioe1, exitCode);
+ }
+ }
+ return exitCode;
+ } catch (IOException ioe2) {
+ throw compilerInteractionException("I/O error when trying to invoke the compiler application", ioe2, exitCode);
+ }
+ }
+ }
+
+ /** Execution under complete control of external app - use externally supplied method to recompile classes */
+ private int recompileUpdatedJavaFilesUsingExternalMethod() {
+ int filesNo = updatedJavaFiles.size() + newJavaFiles.size();
+ String[] fileNames = new String[filesNo];
+ int i = 0;
+ for (String updatedFile : updatedJavaFiles) {
+ recompiledJavaFiles.add(fileNames[i] = updatedFile);
+ }
+ for (int j = 0; j < newJavaFiles.size(); j++) {
+ recompiledJavaFiles.add(fileNames[i++] = newJavaFiles.get(j));
+ }
+
+ try {
+ Integer res =
+ (Integer) externalCompileSourceFilesMethod.invoke(externalApp, new Object[]{fileNames});
+ return res.intValue();
+ } catch (IllegalAccessException e1) {
+ throw compilerInteractionException("compiler method is not accessible", e1, 0);
+ } catch (IllegalArgumentException e2) {
+ throw compilerInteractionException("illegal arguments passed to compiler method", e2, 0);
+ } catch (InvocationTargetException e3) {
+ throw compilerInteractionException("exception when executing the compiler method", e3, 0);
+ }
+ }
+
+ /**
+ * For each .java file from newJavaFiles, find all of the .class files, the names of which we can
+ * logically deduce (a top-level class with the same name, and all of the nested classes),
+ * and put the info on them into the PCD. Also include any class files from the dependencyFile,
+ * if any. For each .jar file from newJarFiles, find all of the .class files in that archive and
+ * put info on them into the PCD.
+ */
+ private void findClassFilesForNewJavaAndJarFiles() {
+ for (String javaFileFullPath : newJavaFiles) {
+ PCDEntry pcde =
+ findClassFileOnFilesystem(javaFileFullPath, null, null, false);
+
+ if (pcde == null) {
+ // .class file not found - possible compilation error
+ if (missingClassIsOk(javaFileFullPath)) {
+ continue;
+ } else {
+ throw new PrivateException(new PublicExceptions.ClassNameMismatchException(
+ "Could not find class file for " + javaFileFullPath));
+ }
+ }
+ Set<String> entries = new HashSet<String>();
+ if (pcde.checkResult == PCDEntry.CV_NEW) { // It's really a new .java file, not a moved one
+ entries.addAll(findAndUpdateAllNestedClassesForClass(pcde, false));
+ } else {
+ entries.addAll(findAndUpdateAllNestedClassesForClass(pcde, true));
+ }
+ entries.add(pcde.className);
+ if (dependencyFile != null) {
+ Map<String, List<String>> dependencies = parseDependencyFile();
+ List<String> myDeps = dependencies.get(javaFileFullPath);
+ if (myDeps != null) {
+ for (String dependency : myDeps) {
+ if (entries.contains(dependency))
+ continue;
+ findClassFileOnFilesystem(javaFileFullPath, pcde,
+ dependency, false);
+ }
+ }
+ }
+ }
+
+ for (String newJarFile : newJarFiles) {
+ processAllClassesFromJarFile(newJarFile);
+ }
+ }
+
+ /**
+ * Parse an extra dependency file. The format of the file is a series of lines,
+ * each consisting of:
+ * SourceFileName.java -> ClassName
+ * (these file names are relative to destDir)
+ */
+ private Map<String, List<String>> parseDependencyFile() {
+ if (!destDirSpecified)
+ throw new RuntimeException("Dependency files require destDir");
+ if (extraDependencies != null)
+ return extraDependencies;
+ BufferedReader in = null;
+ try {
+ extraDependencies = new HashMap<String, List<String>>();
+ in = new BufferedReader(new FileReader(dependencyFile));
+ int lineNumber = 0;
+ while (true) {
+ lineNumber ++;
+ String line = in.readLine();
+ if (line == null)
+ break;
+ String[] parts = line.split("->");
+ if (parts.length != 2) {
+ throw new RuntimeException("Failed to parse line " + lineNumber + " of " + dependencyFile
+ + ". Expected {foo.java} -> {classname}.");
+ }
+ String src = parts[0].trim();
+ src = new File(destDir, src).getCanonicalPath();
+ String cls = parts[1].trim();
+ List<String> classes = extraDependencies.get(src);
+ if (classes == null) {
+ classes = new ArrayList<String>();
+ extraDependencies.put(src, classes);
+ }
+ cls = cls.substring(0, cls.length() - 6); // strip trailing ".class"
+ classes.add(cls);
+ }
+ } catch (IOException e) {
+ throw new PrivateException(e);
+ } finally {
+ if (in != null)
+ try {
+ in.close();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return extraDependencies;
+ }
+
+ /**
+ * In most cases we want to fail the build if a class cannot be found.
+ *
+ * However there is one common valid case where a .java file might not contain
+ * a class: package-info.java files.
+ *
+ * See this doc for more info: http://docs.oracle.com/javase/specs/jls/se7/html/jls-7.html
+ */
+ private boolean missingClassIsOk(String javaFileFullPath) {
+ return javaFileFullPath != null && "package-info.java".equals(new File(javaFileFullPath).getName());
+ }
+
+ /**
+ * Find the .class file for the given javaFileFullPath and create a new PCDEntry for it.
+ * If enclosingClassPCDE is null, the named top-level class for the given .java file is looked up.
+ * Otherwise, the specified class specified by nestedClassFullName is looked up.
+ */
+ private PCDEntry findClassFileOnFilesystem(String javaFileFullPath, PCDEntry enclosingClassPCDE, String nestedClassFullName, boolean isNested) {
+ String classFileFullPath = null;
+ String fullClassName;
+ File classFile = null;
+
+ if (enclosingClassPCDE == null) { // Looking for a top-level class. May need to locate an appropriate directory.
+ // Remove the ".java" suffix. A Windows disk-name prefix, such as 'c:', will be cut off later automatically
+ fullClassName =
+ javaFileFullPath.substring(0, javaFileFullPath.length() - 5);
+ if (destDirSpecified) {
+ // Search for the .class file. We first assume the longest possible name. In case of failure,
+ // we cut the assumed top-most package from it and repeat the search.
+ while (classFile == null) {
+ classFileFullPath = destDir + fullClassName + ".class";
+ classFile = Utils.checkFileForName(classFileFullPath);
+ if (classFile == null) {
+ int cutIndex = fullClassName.indexOf(File.separatorChar);
+ if (cutIndex == -1) {
+ // Most probably, there was an error during compilation of this file.
+ // This does not prevent us from continuing.
+ Utils.printWarningMessage("Warning: unable to find .class file corresponding to source " + javaFileFullPath + ": expected " + classFileFullPath);
+
+ return null;
+ }
+ fullClassName = fullClassName.substring(cutIndex + 1);
+ }
+ }
+ } else {
+ classFileFullPath = fullClassName + ".class";
+ classFile = Utils.checkFileForName(classFileFullPath);
+ if (classFile == null) {
+ Utils.printWarningMessage("Warning: unable to find .class file corresponding to source " + javaFileFullPath);
+ return null;
+ }
+ }
+ } else { // Looking for a nested class, which always sits in the same directory as its enclosing class
+ classFileFullPath =
+ Utils.getClassFileFullPathForNestedClass(enclosingClassPCDE.classFileFullPath, nestedClassFullName);
+ classFile = Utils.checkFileForName(classFileFullPath);
+ if (classFile == null) {
+ Utils.printWarningMessage("Warning: unable to find .class file corresponding to nested class " + nestedClassFullName);
+ return null;
+ }
+ fullClassName = nestedClassFullName;
+ }
+
+ if (backSlashFileSeparator) {
+ fullClassName = fullClassName.replace(File.separatorChar, '/');
+ }
+
+ byte classFileBytes[] = Utils.readFileIntoBuffer(classFile);
+ ClassInfo classInfo =
+ new ClassInfo(classFileBytes, ClassInfo.VER_NEW, this, classFileFullPath);
+ if (isNested) {
+ if (!classInfo.directlyEnclosingClass.equals(enclosingClassPCDE.newClassInfo.name)) {
+ // Check if the above strings are like A and A$1. If so, there is actually no problem - the correct
+ // answer is A$1. The reason why just A was determined as a directly enclosing class when parsing
+ // class classInfo is due to the ambiguous interpretation of names like A$1$B. Such a name may mean
+ // (1) a non-member local nested class B of A, or (2) a member class B of an anonymous nested class A$1.
+ // When parsing any non-toplevel class, the first interpretation is always used.
+ // NOTE FOR JDK 1.5 - starting from this version, there is no ambiguity anymore.
+ // (1) will be called A$1B, and (2) will still be A$1$B
+ String a = classInfo.directlyEnclosingClass;
+ String ad1 = enclosingClassPCDE.newClassInfo.name;
+ if (!((classInfo.javacTargetRelease == Utils.JAVAC_TARGET_RELEASE_OLDEST) &&
+ (ad1.startsWith(a + "$") && Character.isDigit(ad1.charAt(a.length() + 1))))) {
+ throw new PrivateException(new PublicExceptions.ClassFileParseException(
+ "Enclosing class names for class " + classInfo.name + " don't match:\n" +
+ classInfo.directlyEnclosingClass + " and " + enclosingClassPCDE.newClassInfo.name));
+ }
+ }
+ }
+
+ // If dest dir was specified, check if the deduced name is equal to the one in this class (in this case
+ // they should necessarily match). Otherwise, without parsing the .java file, we can't reliably say what the
+ // full class name (actually, its package part) should be - so we just note the name.
+ if (destDirSpecified) {
+ if (!fullClassName.equals(classInfo.name)) {
+ throw new PrivateException(new PublicExceptions.ClassNameMismatchException(
+ "Error: deduced class name is different from the real one for source " +
+ javaFileFullPath + "\n" + fullClassName + " and " + classInfo.name));
+ }
+ } else {
+ fullClassName = classInfo.name;
+ }
+
+ if (enclosingClassPCDE != null) {
+ javaFileFullPath = enclosingClassPCDE.javaFileFullPath;
+ }
+ long classFileLastMod = classFile.lastModified();
+ long classFileFP = computeFP(classFileBytes);
+
+ if (pcd.containsKey(fullClassName)) {
+ PCDEntry pcde = pcd.get(fullClassName);
+ // If this entry has already been checked, it's a second entry for the same class, which is illegal.
+ if (pcde.checkResult == PCDEntry.CV_NEWER_FOUND_NEARER) {
+ // Newer copy of same file found in closer layer
+ // Reset to CV_UNCHECKED and skip redundnacy check
+ // as we know this would be redundant
+ pcde.checkResult = PCDEntry.CV_UNCHECKED;
+ } else {
+ if (pcde.checked) {
+ throw new PrivateException(new PublicExceptions.DoubleEntryException(
+ "Two entries for class " + classInfo.name + " detected: " + pcde.javaFileFullPath + " and " + javaFileFullPath));
+ }
+ }
+ // Otherwise, it means that the .java file for this class has been moved. jmake initially interprets
+ // a new source file name as a new class, and it's only at this point that we can actually see that it was
+ // only a move. We update javaFileFullPath for nested classes after we return from here.
+ pcde.javaFileFullPath = javaFileFullPath;
+ pcde.classFileFullPath = classFileFullPath;
+ pcde.newClassInfo = classInfo;
+ if (deletedClasses.contains(fullClassName)) {
+ deletedClasses.remove(fullClassName);
+ }
+ return pcde;
+ }
+
+ PCDEntry pcde = new PCDEntry(fullClassName,
+ javaFileFullPath,
+ classFileFullPath, classFileLastMod, classFileFP,
+ classInfo);
+ pcde.checkResult = PCDEntry.CV_NEW; // So that later it's promoted into oldClassInfo correctly
+ updatedAndCheckedClasses.add(fullClassName); // So that the above happens
+ pcd.put(fullClassName, pcde);
+ return pcde;
+ }
+
+ /**
+ * For the given class, find all direct nested classes (which may include reading their .class files from the
+ * class path) and set their access flags (contained in this, enclosing class, object) appropriately. If
+ * this class is a one coming from a .java source, repeat the procedure for each nested class in turn.
+ * Otherwise, i.e. if a class comes from a .jar, don't bother, since we will come across each of these
+ * classes anyway - when scanning their .jar. If 'move' parameter is true, it means that this method is called for
+ * a class that is not new, but has been moved (and possibly updated).
+ */
+ private Set<String> findAndUpdateAllNestedClassesForClass(PCDEntry pcde, boolean move) {
+ ClassInfo classInfo = pcde.newClassInfo;
+ if (classInfo.nestedClasses == null) {
+ return Collections.emptySet();
+ }
+ Set<String> entries = new LinkedHashSet<String>();
+ String nestedClasses[] = classInfo.nestedClasses;
+ String javaFileFullPath = pcde.javaFileFullPath;
+ String enclosingClassFileFullPath = pcde.classFileFullPath;
+ boolean isJavaSourceFile = javaFileFullPath.endsWith(".java");
+
+ for (int i = 0; i < nestedClasses.length; i++) {
+ PCDEntry nestedPCDE = pcd.get(nestedClasses[i]);
+ if (nestedPCDE == null) {
+ if (isJavaSourceFile) {
+ nestedPCDE =
+ findClassFileOnFilesystem(null, pcde, nestedClasses[i], true);
+ }
+ // For classes that come from a .jar, pcde should already be there. Otherwise this class just doesn't exist.
+ if (nestedPCDE == null) {
+ // Probably a compilation error, such that enclosing class is compiled but nested is not.
+ throw new PrivateException(new PublicExceptions.ClassNameMismatchException(
+ "Could not find class file for " + pcde.toString()));
+ }
+ }
+ if (move) {
+ if (deletedClasses.contains(nestedClasses[i])) {
+ deletedClasses.remove(nestedClasses[i]);
+ }
+ nestedPCDE.javaFileFullPath = javaFileFullPath;
+ if (javaFileFullPath.endsWith(".java")) {
+ nestedPCDE.classFileFullPath =
+ Utils.getClassFileFullPathForNestedClass(enclosingClassFileFullPath, nestedClasses[i]);
+ } else {
+ nestedPCDE.classFileFullPath = javaFileFullPath;
+ }
+ }
+ if (nestedPCDE.newClassInfo == null) {
+ getClassInfoForPCDEntry(ClassInfo.VER_NEW, nestedPCDE);
+ }
+ nestedPCDE.newClassInfo.accessFlags =
+ pcde.newClassInfo.nestedClassAccessFlags[i];
+ nestedPCDE.newClassInfo.isNonMemberNestedClass =
+ pcde.newClassInfo.nestedClassNonMember[i];
+
+ entries.add(nestedPCDE.className);
+ entries.addAll(findAndUpdateAllNestedClassesForClass(nestedPCDE, move));
+ }
+ return entries;
+ }
+
+ /**
+ * Take care of new nested classes that could have been generated from already existing .java sources,
+ * and of nested classes that do not exist anymore because they were deleted from these sources.
+ */
+ private void dealWithNestedClassesForUpdatedJavaFiles() {
+ if (updatedJavaFiles.size() == 0) {
+ return;
+ }
+
+ // First put PCDEntries for all updated classes that have nested classes into a temporary list.
+ // That's because we can then find new nested classes, which we will need to add to the PCD, which
+ // may probably conflict with us still iterating over it.
+ List<PCDEntry> updatedEntries = new ArrayList<PCDEntry>();
+ for (PCDEntry pcde : entries()) {
+ if (pcde.checkResult == PCDEntry.CV_NEW) {
+ continue; // This class has just been added to the PCD
+ }
+ if (updatedJavaFiles.contains(pcde.javaFileFullPath)) {
+ ClassInfo oldClassInfo = pcde.oldClassInfo;
+ ClassInfo newClassInfo =
+ getClassInfoForPCDEntry(ClassInfo.VER_NEW, pcde);
+ if (newClassInfo == null) {
+ deletedClasses.add(pcde.className);
+ continue; // Class file deleted then not re-created due to a compilation error somewhere.
+ }
+ if (oldClassInfo.nestedClasses != null || newClassInfo.nestedClasses != null) {
+ updatedEntries.add(pcde);
+ }
+ }
+ }
+
+ if (dependencyFile != null) {
+ Map<String, List<String>> dependencies = parseDependencyFile();
+ for (String file : updatedJavaFiles) {
+ List<String> myDeps = dependencies.get(file);
+ if (myDeps == null)
+ continue;
+ PCDEntry pcde = getNamedPCDE(file, dependencies);
+ for (String dependency : myDeps) {
+ PCDEntry dep = pcd.get(dependency);
+ if (dep != null)
+ // This is an existing dep.
+ continue;
+ dep = findClassFileOnFilesystem(file, pcde, dependency, false);
+ getClassInfoForPCDEntry(ClassInfo.VER_NEW, dep);
+ if (dep.newClassInfo.nestedClasses != null)
+ updatedEntries.add(dep);
+ }
+ }
+ }
+ dealWithNestedClassesForUpdatedPCDEntries(updatedEntries, false);
+ }
+
+ private PCDEntry getNamedPCDE(String file, Map<String, List<String>> dependencies) {
+ List<String> depsForFile = dependencies.get(file);
+ PCDEntry pcde = null;
+ // Find a non-nested class for this java file for which we already have
+ // a pcde
+ for (String dependency : depsForFile) {
+ if (dependency.indexOf('$') != -1)
+ continue;
+ pcde = pcd.get(dependency);
+ if (pcde != null)
+ break;
+ }
+ if (pcde == null) {
+ throw new PrivateException(new PublicExceptions.InternalException(file
+ + " was supposed to be an updated file, but there are no PCDEntries for any of its deps"));
+ }
+ return pcde;
+ }
+
+ private void dealWithNestedClassesForUpdatedPCDEntries(List<PCDEntry> entries, boolean move) {
+ for (int i = 0; i < entries.size(); i++) {
+ PCDEntry pcde = entries.get(i);
+ ClassInfo oldClassInfo = pcde.oldClassInfo;
+ ClassInfo newClassInfo = pcde.newClassInfo;
+ if (newClassInfo.nestedClasses != null) {
+ Set<String> nested = findAndUpdateAllNestedClassesForClass(pcde, move);
+ if (oldClassInfo.nestedClasses != null) { // Check if any old nested classes don't exist anymore
+ for (int j = 0; j < oldClassInfo.nestedClasses.length; j++) {
+ boolean found = false;
+ String oldNestedClass = oldClassInfo.nestedClasses[j];
+ for (int k = 0; k < newClassInfo.nestedClasses.length; k++) {
+ if (oldNestedClass.equals(newClassInfo.nestedClasses[k])) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ deletedClasses.add(oldNestedClass);
+ }
+ }
+ }
+ } else { // newNestedClasses == null and oldNestedClasses != null, so all nested classes have been removed in the new version
+ for (int j = 0; j < oldClassInfo.nestedClasses.length; j++) {
+ deletedClasses.add(oldClassInfo.nestedClasses[j]);
+ }
+ }
+ }
+ }
+
+ private void findUpdatedClasses() {
+ // This (iterating over all of the classes once again after performing that in classFileObsoleteOrDeleted()) may
+ // seem time-consuming, but in reality it isn't, since the most time-consuming operation of obtaining internal
+ // file handles for class files has already been performed in classFileObsoleteOrDeleted(). Once we have done that,
+ // this re-iteration takes very small amount of time. However, if we switch from "class file older than .java
+ // file" to ".java file timestamp changed" condition for recompilation, this will have to be changed as well.
+ for (PCDEntry entry : entries()) {
+ String className = entry.className;
+ if (updatedAndCheckedClasses.contains(className) ||
+ deletedClasses.contains(className)) {
+ continue;
+ }
+ if (!entry.javaFileFullPath.endsWith(".java")) {
+ continue; // classes from (updated) .jars have been dealt with separately
+ }
+ //DAB TODO understand this bit better. It is needed to support -vpath, I'm just not sure why....
+ if (entry.checkResult != PCDEntry.CV_NEWER_FOUND_NEARER &&
+ !updatedAndCheckedClasses.contains(className) &&
+ !deletedClasses.contains(className) &&
+ entry.javaFileFullPath.endsWith(".java") &&
+ classFileUpdated(entry))
+ {
+ //DAB TODO this is the old way....
+ //DAB if (classFileUpdated(entry)) {
+ updatedClasses.add(className);
+ allUpdatedClasses.add(className);
+ }
+ }
+ }
+
+ private boolean classFileUpdated(PCDEntry entry) {
+ File classFile = Utils.checkFileForName(entry.classFileFullPath);
+ if (classFile == null) {
+ return false;
+ }
+ // The only case when the above can happen is if class file was first deleted, and then there
+ // was an error recompiling its source
+
+ long classFileLastMod = classFile.lastModified();
+
+ if (classFileLastMod > entry.oldClassFileLastModified) {
+ entry.newClassFileLastModified = classFileLastMod;
+ // Check if the class was actually modified, to avoid the costly procedure of detailed version compare
+ long classFileFP = computeFP(classFile);
+ if (classFileFP != entry.oldClassFileFingerprint) {
+ entry.newClassFileFingerprint = classFileFP;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Compare old (preserved in pdb) and new (file system) versions of updated classes, and find all
+ * potentially affected dependent classes.
+ */
+ private void checkUpdatedClasses() {
+ for (String className : updatedClasses) {
+ PCDEntry pcde = pcd.get(className);
+ getClassInfoForPCDEntry(ClassInfo.VER_NEW, pcde);
+ if (!"".equals(pcde.oldClassInfo.directlyEnclosingClass)) {
+ // The following problem can occur with nested classes. A C.java source has been changed, so that C.class is
+ // not changed or changed in a compatible way, whereas the access modifiers of C$X.class are changed in an
+ // incompatible way, so that something is broken in the project. When jmake is called for the first time,
+ // it reports the problem, then saves the info on the new version of C in the pdb. Of course, the record for
+ // C$X in the pdb is not updated, since the change to it is incompatible and recompilation of dependent sources
+ // has failed. Suppose we don't change anything and invoke jmake again. C$X is found different from its old
+ // version and is checked here again. The outcome should be the same. But since C has not changed, C.class is
+ // not read from disk and the access flags of C$X, which are stored in C.class, are not set appropriately. So
+ // in such circumstances we have wrong access flags for C$X here. To fix the problem we need to load C explicitly.
+ ClassInfo enclosingClassInfo =
+ getClassInfoForName(ClassInfo.VER_NEW, pcde.oldClassInfo.directlyEnclosingClass);
+ //if (enclosingClassInfo == null || enclosingClassInfo.nestedClasses == null) {
+ // System.out.println("!!! Suspicious updated class name = " + className);
+ // System.out.println("!!! enclosingClassInfo for it = " + enclosingClassInfo);
+ // if (enclosingClassInfo != null) {
+ // System.out.println("!!! enclosingClassInfo.name = " + enclosingClassInfo.name);
+ // if (enclosingClassInfo.nestedClasses == null) System.out.println("!!! enclosingClassInfo.nestedClasses = null");
+ // }
+ //}
+ if (enclosingClassInfo.nestedClasses != null) { // Can be that this nested class was the only one for enclosing class, and it's deleted now
+ for (int i = 0; i < enclosingClassInfo.nestedClasses.length; i++) {
+ if (className.equals(enclosingClassInfo.nestedClasses[i])) {
+ pcde.newClassInfo.accessFlags =
+ enclosingClassInfo.nestedClassAccessFlags[i];
+ pcde.newClassInfo.isNonMemberNestedClass =
+ enclosingClassInfo.nestedClassNonMember[i];
+ break;
+ }
+ }
+ }
+ }
+ if (!(pcde.oldClassInfo.isNonMemberNestedClass && pcde.newClassInfo.isNonMemberNestedClass)) {
+ Utils.printInfoMessage("Checking " + pcde.className);
+ pcde.checkResult = cv.compareClassVersions(pcde) ? PCDEntry.CV_COMPATIBLE
+ : PCDEntry.CV_INCOMPATIBLE;
+ String affectedClasses[] = cv.getAffectedClasses();
+ if (affectedClasses != null) {
+ for (int i = 0; i < affectedClasses.length; i++) {
+ PCDEntry affEntry = pcd.get(affectedClasses[i]);
+ updatedJavaFiles.add(affEntry.javaFileFullPath);
+ }
+ }
+ } else {
+ // A non-member nested class can not be referenced by the source code of any class defined outside the
+ // immediately enclosing source code block for this class. Therefore, any incompatibility in the new
+ // version of this class can affect only classes that are defined in the same source file - and they
+ // are necessarily recompiled together with this class. So there is no point in initiating version
+ // compare for this class. However, the new class version should always tembe promoted into the store, since
+ // this class itself may depend on other changing classes.
+ pcde.checkResult = PCDEntry.CV_COMPATIBLE;
+ }
+
+ updatedAndCheckedClasses.add(className);
+ }
+ }
+
+ /** Find all dependent classes for deleted classes. */
+ private void checkDeletedClasses() {
+ for (String className : deletedClasses) {
+ PCDEntry pcde = pcd.get(className);
+
+ if (pcde == null) { // "Safety net" for the (hopefully unlikely) case. I observed it just once and couldn't identify the reason
+ Utils.printWarningMessage("Warning: internal information inconsistency when checking deleted classes");
+ Utils.printWarningMessage(Utils.REPORT_PROBLEM);
+ Utils.printWarningMessage("Class name: " + className);
+ continue;
+ }
+
+ ClassInfo oldCI = pcde.oldClassInfo;
+ if (!oldCI.isNonMemberNestedClass) { // See the comment above
+ Utils.printInfoMessage("Checking deleted class " + oldCI.name);
+ cv.checkDeletedClass(pcde);
+ String[] affectedClasses = cv.getAffectedClasses();
+ if (affectedClasses != null) {
+ for (int i = 0; i < affectedClasses.length; i++) {
+ PCDEntry affEntry = pcd.get(affectedClasses[i]);
+ if (deletedClasses.contains(affEntry.className)) {
+ continue;
+ }
+ updatedJavaFiles.add(affEntry.javaFileFullPath);
+ }
+ }
+ }
+ pcde.checkResult = PCDEntry.CV_DELETED;
+ updatedAndCheckedClasses.add(className);
+ }
+ deletedClasses.clear();
+ }
+
+ /**
+ * Determine what classes in the given .jar (which may be an existing updated one, or a new one) are new,
+ * updated, or moved, and treat them accordingly.
+ */
+ private void processAllClassesFromJarFile(String jarFileName) {
+ JarFile jarFile;
+ long jarFileLastMod = 0;
+ try {
+ File file = new File(jarFileName);
+ jarFileLastMod = file.lastModified();
+ jarFile = new JarFile(jarFileName);
+ } catch (IOException ex) {
+ throw new PrivateException(ex);
+ }
+
+ List<PCDEntry> newEntries = new ArrayList<PCDEntry>();
+ List<PCDEntry> updatedEntries = new ArrayList<PCDEntry>();
+ List<PCDEntry> movedEntries = new ArrayList<PCDEntry>();
+
+ for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements();) {
+ JarEntry jarEntry = entries.nextElement();
+ String fullClassName = jarEntry.getName();
+ if (!fullClassName.endsWith(".class")) {
+ continue;
+ }
+ fullClassName =
+ fullClassName.substring(0, fullClassName.length() - 6).intern();
+ byte classFileBytes[];
+ classFileBytes = Utils.readZipEntryIntoBuffer(jarFile, jarEntry);
+ long classFileFP = computeFP(classFileBytes);
+
+ PCDEntry pcde = pcd.get(fullClassName);
+ if (pcde != null) {
+ if (pcde.checked) {
+ throw new PrivateException(new PublicExceptions.DoubleEntryException(
+ "Two entries for class " + fullClassName + " detected: " + pcde.javaFileFullPath + " and " + jarFileName));
+ }
+ pcde.checked = true;
+ pcde.newClassFileLastModified = jarFileLastMod;
+ // If we are scanning an existing updated .jar file, and there is no change to the class itself,
+ // and it previously was located in the same .jar, do nothing.
+ if (pcde.oldClassFileFingerprint == classFileFP &&
+ pcde.javaFileFullPath.equals(jarFileName)) {
+ pcde.oldClassFileLastModified = jarFileLastMod; // So that next time jmake is inoked, checking
+ continue; // of this.jar is not triggered.
+ }
+ if (pcde.oldClassFileFingerprint != classFileFP) { // This class has been updated
+ updatedClasses.add(fullClassName);
+ allUpdatedClasses.add(fullClassName);
+ pcde.newClassFileLastModified = jarFileLastMod;
+ pcde.newClassFileFingerprint = classFileFP;
+ pcde.newClassInfo =
+ new ClassInfo(classFileBytes, ClassInfo.VER_NEW, this, fullClassName);
+ if (pcde.oldClassInfo.nestedClasses != null || pcde.newClassInfo.nestedClasses != null) {
+ updatedEntries.add(pcde);
+ }
+ } else {
+ pcde.oldClassFileLastModified = jarFileLastMod;
+ }
+ if (!pcde.javaFileFullPath.equals(jarFileName)) {
+ // Found an existing class in a different .jar file.
+ // May happen if the class file has been moved from one .jar to another (or into a .jar, losing its
+ // .java source). It's only at this point that we can actually see that it was really a move.
+ if (deletedClasses.contains(fullClassName)) {
+ deletedClasses.remove(fullClassName);
+ }
+ if (pcde.oldClassInfo.nestedClasses != null) {
+ movedEntries.add(pcde);
+ pcde.newClassInfo =
+ new ClassInfo(classFileBytes, ClassInfo.VER_NEW, this, fullClassName);
+ }
+ }
+ pcde.javaFileFullPath = jarFileName;
+ } else { // New class file
+ ClassInfo classInfo =
+ new ClassInfo(classFileBytes, ClassInfo.VER_NEW, this, fullClassName);
+ pcde = new PCDEntry(fullClassName,
+ jarFileName,
+ jarFileName, jarFileLastMod, classFileFP,
+ classInfo);
+ pcde.checkResult = PCDEntry.CV_NEW; // So that later it's promoted into oldClassInfo correctly
+ updatedAndCheckedClasses.add(fullClassName); // So that the above happens
+ pcd.put(fullClassName, pcde);
+ if (pcde.newClassInfo.nestedClasses != null) {
+ newEntries.add(pcde);
+ }
+ }
+ }
+
+ dealWithNestedClassesForUpdatedPCDEntries(updatedEntries, false);
+ dealWithNestedClassesForUpdatedPCDEntries(movedEntries, true);
+ for (int i = 0; i < newEntries.size(); i++) {
+ findAndUpdateAllNestedClassesForClass(newEntries.get(i), false);
+ }
+ }
+
+ /** Determine new, deleted and updated classes coming from updated .jar files. */
+ private void dealWithClassesInUpdatedJarFiles() {
+ if (updatedJarFiles.size() == 0) {
+ return;
+ }
+
+ for (String updatedJarFile : updatedJarFiles) {
+ processAllClassesFromJarFile(updatedJarFile);
+ }
+
+ // Now scan the PCD to check which classes that come from updated .jar files have not been marked as checked
+ for (PCDEntry pcde : entries()) {
+ if (updatedJarFiles.contains(pcde.javaFileFullPath)) {
+ if (!pcde.checked) {
+ deletedClasses.add(pcde.className);
+ }
+ }
+ }
+ }
+
+ /** Check if the destination directory exists, and get the canonical path for it. */
+ private void initializeDestDir(String inDestDir) {
+ if (!(inDestDir == null || inDestDir.equals(""))) {
+ File dir = Utils.checkOrCreateDirForName(inDestDir);
+ if (dir == null) {
+ throw new PrivateException(new IOException("specified directory " + inDestDir + " cannot be created."));
+ }
+ inDestDir = getCanonicalPath(dir);
+ if (!inDestDir.endsWith(File.separator)) {
+ inDestDir += File.separatorChar;
+ }
+ destDir = inDestDir;
+ destDirSpecified = true;
+ } else {
+ destDirSpecified = false;
+ }
+ }
+
+ /**
+ * For the given PCDEntry, set the entry.classFileFullPath according to the value of the .java file full
+ * path and the value of the "-d" option at this particular jmake invocation
+ */
+ private void initializeClassFileFullPath(PCDEntry entry) {
+ String classFileFullPath;
+ if (destDirSpecified) {
+ classFileFullPath = destDir + entry.className + ".class";
+ } else {
+ String javaFileDir = entry.javaFileFullPath;
+ int cutIndex = javaFileDir.lastIndexOf(File.separatorChar);
+ if (cutIndex != -1) {
+ javaFileDir = javaFileDir.substring(0, cutIndex + 1);
+ }
+ String classFileName = entry.className;
+ cutIndex = classFileName.lastIndexOf('/');
+ if (cutIndex != -1) {
+ classFileName = classFileName.substring(cutIndex + 1);
+ }
+ classFileFullPath = javaFileDir + classFileName + ".class";
+ }
+ if (backSlashFileSeparator) {
+ classFileFullPath =
+ classFileFullPath.replace('/', File.separatorChar);
+ }
+ entry.classFileFullPath = classFileFullPath;
+ }
+
+ private static String getCanonicalPath(File file) {
+ try {
+ return file.getCanonicalPath().intern();
+ } catch (IOException e) {
+ throw new PrivateException(e);
+ }
+ }
+
+ private long computeFP(File file) {
+ byte buf[] = Utils.readFileIntoBuffer(file);
+ return computeFP(buf);
+ }
+
+ private long computeFP(byte[] buf) {
+ checkSum.reset();
+ checkSum.update(buf);
+ return checkSum.getValue();
+ }
+
+ private PrivateException compilerInteractionException(String message, Exception origException, int errCode) {
+ return new PrivateException(new PublicExceptions.CompilerInteractionException(message, origException, errCode));
+ }
+
+ private PrivateException internalException(String message) {
+ return new PrivateException(new PublicExceptions.InternalException(message));
+ }
+}
diff --git a/third_party/jmake/src/org/pantsbuild/jmake/PrivateException.java b/third_party/jmake/src/org/pantsbuild/jmake/PrivateException.java
new file mode 100644
index 0000000..b8b0c78
--- /dev/null
+++ b/third_party/jmake/src/org/pantsbuild/jmake/PrivateException.java
@@ -0,0 +1,28 @@
+/* Copyright (c) 2002-2008 Sun Microsystems, Inc. All rights reserved
+ *
+ * This program is distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+package org.pantsbuild.jmake;
+
+/**
+ * This class is used as a wrapper for a number of exceptions that are thrown by jmake. Its
+ * only purpose is to help avoid using endless "throws" clauses in the code.
+ *
+ * @author Misha Dmitriev
+ * 12 November 2001
+ */
+public class PrivateException extends RuntimeException {
+
+ private static final long serialVersionUID = 1L;
+ private Throwable originalException;
+
+ public PrivateException(Throwable e) {
+ originalException = e;
+ }
+
+ public Throwable getOriginalException() {
+ return originalException;
+ }
+}
diff --git a/third_party/jmake/src/org/pantsbuild/jmake/PublicExceptions.java b/third_party/jmake/src/org/pantsbuild/jmake/PublicExceptions.java
new file mode 100644
index 0000000..4009745
--- /dev/null
+++ b/third_party/jmake/src/org/pantsbuild/jmake/PublicExceptions.java
@@ -0,0 +1,169 @@
+/* Copyright (c) 2002-2008 Sun Microsystems, Inc. All rights reserved
+ *
+ * This program is distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+package org.pantsbuild.jmake;
+
+/**
+ * This class is a wrapper for a number of nested classes. They define exceptions that may be thrown
+ * by the <b>jmake</b> code. These exceptions are caught in the <code>Main.mainExternal</code> method,
+ * or they may be caught and handled by an application invoking <b>jmake</b> programmatically
+ * through <code>Main.mainProgrammatic</code> method.
+ *
+ * @see Main#mainExternal(String[])
+ * @see Main#mainProgrammatic(String[])
+ *
+ * @author Misha Dmitriev
+ * 17 January 2003
+ */
+public class PublicExceptions {
+
+ /**
+ * This exception is thrown whenever there is any problem initializing or calling the compiler, or
+ * when the compiler itself reports compilation errors. Depending on the nature of the problem, an
+ * instance of this class is initialized with, and then returns, an original exception signalling
+ * the problem, or the compiler application exit code.
+ */
+ public static class CompilerInteractionException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+ private Exception originalException;
+ private int compilerExitCode;
+
+ /**
+ * Initialize an instance of this exception with an error message and either an original
+ * exception or a compiler exit code.
+ */
+ CompilerInteractionException(String msg, Exception originalException, int compilerExitCode) {
+ super(msg);
+ this.originalException = originalException;
+ this.compilerExitCode = compilerExitCode;
+ }
+
+ /**
+ * Get the original exception that caused this exception. <code>null</code> is returned if there
+ * were no original exception. In that case, there was a compilation error and the compiler has
+ * returned with the exit code that can be obtained using <code>getCompilerExitCode</code> method.
+ *
+ * @see #getCompilerExitCode()
+ */
+ public Exception getOriginalException() {
+ return originalException;
+ }
+
+ /**
+ * Get the compiler exit code. If <code>0</code> is returned, this indicates that an exception was
+ * thrown during compiler initialization, or inside the compiler itself. Use <code>getOriginalExcepion</code>
+ * method to obtain that exception.
+ *
+ * @see #getOriginalException
+ */
+ public int getCompilerExitCode() {
+ return compilerExitCode;
+ }
+ }
+
+ /** Exception signalling a problem with reading project database file. */
+ public static class PDBCorruptedException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ PDBCorruptedException(String msg) {
+ super(msg);
+ }
+ }
+
+ /** Exception signalling a problem when parsing a class file */
+ public static class ClassFileParseException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ ClassFileParseException(String msg) {
+ super(msg);
+ }
+ }
+
+ /** Exception signalling that <b>jmake</b> was not requested to do any real work. */
+ public static class NoActionRequestedException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+ }
+
+ /** Exception signalling that an invalid command line option has been passed to jmake. */
+ public static class InvalidCmdOptionException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ InvalidCmdOptionException(String msg) {
+ super(msg);
+ }
+ }
+
+ /**
+ * Exception signalling a problem (typically an I/O exception) when parsing a command line file
+ * passed to <b>jmake</b>.
+ */
+ public static class CommandFileReadException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ CommandFileReadException(String msg) {
+ super(msg);
+ }
+ }
+
+ /**
+ * Exception signalling that for some class the deduced name (that is, name based on the source file name and
+ * directory) and the real name (the one read from the class file) are different.
+ */
+ public static class ClassNameMismatchException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ ClassNameMismatchException(String msg) {
+ super(msg);
+ }
+ }
+
+ /** Exception thrown if any specified source file has an invalid extension (not <code>.java</code> or <code>.jar</code>). */
+ public static class InvalidSourceFileExtensionException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ InvalidSourceFileExtensionException(String msg) {
+ super(msg);
+ }
+ }
+
+ /** Exception thrown if a dependence of a class in a <code>JAR</code> file on a class with a <code>.java</code> source is detected. */
+ public static class JarDependsOnSourceException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ JarDependsOnSourceException(String msg) {
+ super(msg);
+ }
+ }
+
+ /** Exception thrown if more than one entry for the same class is detected in the project */
+ public static class DoubleEntryException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ DoubleEntryException(String msg) {
+ super(msg);
+ }
+ }
+
+ /** Exception thrown if an internal problem that should never happen is detected. */
+ public static class InternalException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ InternalException(String msg) {
+ super(msg);
+ }
+ }
+}
diff --git a/third_party/jmake/src/org/pantsbuild/jmake/RefClassFinder.java b/third_party/jmake/src/org/pantsbuild/jmake/RefClassFinder.java
new file mode 100644
index 0000000..58c8884
--- /dev/null
+++ b/third_party/jmake/src/org/pantsbuild/jmake/RefClassFinder.java
@@ -0,0 +1,697 @@
+/* Copyright (c) 2002-2008 Sun Microsystems, Inc. All rights reserved
+ *
+ * This program is distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+package org.pantsbuild.jmake;
+
+import java.lang.reflect.Modifier;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * This class implements finding classes referencing other classes and members in various ways.
+ *
+ * @author Misha Dmitriev
+ * 12 March 2004
+ */
+public class RefClassFinder {
+
+ private boolean failOnDependentJar; // If true, will fail if a dependency of a sourceless class
+ // (coming from a .jar) on a "normal" class is detected
+ private boolean noWarnOnDependentJar; // If true, not even a warning will be issued in the above case.
+ private String checkedClassName;
+ private PCDManager pcdm;
+ private Set<String> affectedClassNames;
+ private boolean checkedClassIsFromJar;
+
+ /** An instance of RefClassFinder is created once per session, passing it the global options that do not change */
+ public RefClassFinder(PCDManager pcdm, boolean failOnDependentJar, boolean noWarnOnDependentJar) {
+ this.pcdm = pcdm;
+ this.failOnDependentJar = failOnDependentJar;
+ this.noWarnOnDependentJar = noWarnOnDependentJar;
+ }
+
+ /** This method is called every time we are going to check a new class */
+ public void initialize(String checkedClassName, boolean checkedClassIsFromJar) {
+ this.checkedClassName = checkedClassName;
+ this.checkedClassIsFromJar = checkedClassIsFromJar;
+ affectedClassNames = new LinkedHashSet<String>();
+ }
+
+ /**
+ * Returns the names of project classes that were found potentially affec
+ * by the changes to the checked class.
+ */
+ public String[] getAffectedClassNames() {
+ int size = affectedClassNames.size();
+ if (size == 0) {
+ return null;
+ } else {
+ String[] ret = new String[size];
+ int i = 0;
+ for (String className : affectedClassNames) {
+ ret[i++] = className;
+ }
+ return ret;
+ }
+ }
+
+ /**
+ * Find all project classes that can access field fieldNo of class fieldClassInfo.
+ * Used if a compile-time constant is changed.
+ */
+ public void findAllProjectClasses(ClassInfo fieldClassInfo, int fieldNo) {
+ for (PCDEntry pcde : pcdm.entries()) {
+ if (pcde.checkResult == PCDEntry.CV_DELETED) {
+ continue;
+ }
+ if (pcde.javaFileFullPath.endsWith(".jar")) {
+ continue;
+ }
+ ClassInfo clientInfo =
+ pcdm.getClassInfoForPCDEntry(ClassInfo.VER_OLD, pcde);
+ if (clientInfo == null) {
+ continue; // New class
+ }
+ if (memberAccessibleFrom(fieldClassInfo, fieldNo, clientInfo, true)) {
+ addToAffectedClassNames(clientInfo.name);
+ }
+ }
+ }
+
+ /**
+ * Find all project classes that reference class with the given name
+ * (but not its array class) directly from the constantpool.
+ */
+ public void findReferencingClasses0(ClassInfo classInfo) {
+ findReferencingClasses(classInfo, 0, false, null);
+ }
+
+
+ /* In the following "find...ReferencingClasses1" methods, "referencing C" means
+ * "referencing C or its array class directly from the constant pool, as a type of a data
+ * field, as a type in a method signature or a thrown exception, as a directly implemented
+ * interface or a direct superclass".
+ */
+ /** Used for deleted classes. */
+ public void findReferencingClassesForDeletedClass(ClassInfo classInfo) {
+ String packageName = classInfo.packageName;
+ boolean isPublic = classInfo.isPublic();
+ boolean isInterface = classInfo.isInterface();
+ for (PCDEntry pcde : pcdm.entries()) {
+ ClassInfo clientInfo =
+ pcdm.getClassInfoForPCDEntry(ClassInfo.VER_OLD, pcde);
+ if (clientInfo == null) {
+ continue; // New class
+ }
+ if (!isPublic && packageName.equals(clientInfo.packageName)) {
+ continue;
+ }
+ if (clientInfo.referencesClass(classInfo.name, isInterface, 1)) {
+ addToAffectedClassNames(clientInfo.name);
+ }
+ }
+
+ }
+
+ /**
+ * For the given class p.C, find each project class X referencing C, that is not a member of
+ * package p and is not a direct or indirect subclass of C's directly enclosing class.
+ * (public -&gt; protected transformation)
+ */
+ public void findDiffPackageAndNotSubReferencingClasses1(ClassInfo classInfo) {
+ String packageName = classInfo.packageName;
+ String directlyEnclosingClass = classInfo.directlyEnclosingClass;
+ for (PCDEntry pcde : pcdm.entries()) {
+ ClassInfo clientInfo =
+ pcdm.getClassInfoForPCDEntry(ClassInfo.VER_OLD, pcde);
+ if (clientInfo == null) {
+ continue; // New class
+ }
+ if (packageName.equals(clientInfo.packageName) ||
+ clientInfo.isSubclassOf(directlyEnclosingClass, false)) {
+ continue;
+ }
+ if (clientInfo.referencesClass(classInfo.name, classInfo.isInterface(), 1)) {
+ addToAffectedClassNames(clientInfo.name);
+ }
+ }
+ }
+
+ /**
+ * For class p.C, find each project class X referencing C, whose top level enclosing
+ * class is different from that of C.
+ * (public -&gt; private transformation)
+ */
+ public void findReferencingClasses1(ClassInfo classInfo) {
+ String topLevelEnclosingClass = classInfo.topLevelEnclosingClass;
+ for (PCDEntry pcde : pcdm.entries()) {
+ ClassInfo clientInfo =
+ pcdm.getClassInfoForPCDEntry(ClassInfo.VER_OLD, pcde);
+ if (clientInfo == null) {
+ continue; // New class
+ }
+ if (topLevelEnclosingClass.equals(clientInfo.topLevelEnclosingClass)) {
+ continue;
+ }
+ if (clientInfo.referencesClass(classInfo.name, classInfo.isInterface(), 1)) {
+ addToAffectedClassNames(clientInfo.name);
+ }
+ }
+ }
+
+ /**
+ * For class p.C, find each project class X referencing C, whose direct or indirect superclass
+ * is C's directly enclosing class, or which is a member of package p, whose top level enclosing
+ * class is different from that of C.
+ * (protected -&gt; private transformation)
+ */
+ public void findThisPackageOrSubReferencingClasses1(ClassInfo classInfo) {
+ String directlyEnclosingClass = classInfo.directlyEnclosingClass;
+ String topLevelEnclosingClass = classInfo.topLevelEnclosingClass;
+ String packageName = classInfo.packageName;
+ for (PCDEntry entry : pcdm.entries()) {
+ ClassInfo clientInfo =
+ pcdm.getClassInfoForPCDEntry(ClassInfo.VER_OLD, entry);
+ if (clientInfo == null) {
+ continue; // New class
+ }
+ if ((!clientInfo.packageName.equals(packageName)) &&
+ !clientInfo.isSubclassOf(directlyEnclosingClass, false)) {
+ continue;
+ }
+ if (clientInfo.topLevelEnclosingClass.equals(topLevelEnclosingClass)) {
+ continue;
+ }
+ if (clientInfo.referencesClass(classInfo.name, classInfo.isInterface(), 1)) {
+ addToAffectedClassNames(clientInfo.name);
+ }
+ }
+ }
+
+ /**
+ * For class p.C, find each project class X referencing C, which is a member of package p and whose
+ * top level enclosing class is different from that of C.
+ * (default -&gt; private transformation)
+ */
+ public void findThisPackageReferencingClasses1(ClassInfo classInfo) {
+ String topLevelEnclosingClass = classInfo.topLevelEnclosingClass;
+ String packageName = classInfo.packageName;
+ for (PCDEntry entry : pcdm.entries()) {
+ ClassInfo clientInfo =
+ pcdm.getClassInfoForPCDEntry(ClassInfo.VER_OLD,
+ entry);
+ if (clientInfo == null) {
+ continue; // New class
+ }
+ if (!clientInfo.packageName.equals(packageName)) {
+ continue;
+ }
+ if (topLevelEnclosingClass.equals(clientInfo.topLevelEnclosingClass)) {
+ continue;
+ }
+ if (clientInfo.referencesClass(classInfo.name, classInfo.isInterface(), 1)) {
+ addToAffectedClassNames(clientInfo.name);
+ }
+ }
+ }
+
+ /**
+ * For class p.C, find each project class X referencing C, which is not a member of package p.
+ * (public -&gt; default transformation)
+ */
+ public void findDiffPackageReferencingClasses1(ClassInfo classInfo) {
+ String packageName = classInfo.packageName;
+ for (PCDEntry pcde : pcdm.entries()) {
+ ClassInfo clientInfo =
+ pcdm.getClassInfoForPCDEntry(ClassInfo.VER_OLD, pcde);
+ if (clientInfo == null) {
+ continue; // New class
+ }
+ if (clientInfo.packageName.equals(packageName)) {
+ continue;
+ }
+ if (clientInfo.referencesClass(classInfo.name, classInfo.isInterface(), 1)) {
+ addToAffectedClassNames(clientInfo.name);
+ }
+ }
+ }
+
+ /**
+ * For class p.C, find each project class X referencing C, which is not a member of package p and
+ * whose direct or indirect superclass is C's directly enclosing class.
+ * (protected -&gt; default transformation)
+ */
+ public void findDiffPackageAndSubReferencingClasses1(ClassInfo classInfo) {
+ String packageName = classInfo.packageName;
+ String directlyEnclosingClass = classInfo.directlyEnclosingClass;
+ for (PCDEntry pcde : pcdm.entries()) {
+ ClassInfo clientInfo =
+ pcdm.getClassInfoForPCDEntry(ClassInfo.VER_OLD, pcde);
+ if (clientInfo == null) {
+ continue; // New class
+ }
+ if (clientInfo.packageName.equals(packageName)) {
+ continue;
+ }
+ if (!clientInfo.isSubclassOf(directlyEnclosingClass, false)) {
+ continue;
+ }
+ if (clientInfo.referencesClass(classInfo.name, classInfo.isInterface(), 1)) {
+ addToAffectedClassNames(clientInfo.name);
+ }
+ }
+ }
+
+ /**
+ * Find all project classes that reference both of the classes with the
+ * given names (or array classes of one or both) directly or indirectly from the
+ * constantpool, as a type of a data field, as a type in a method signature or a
+ * thrown exception, as a directly/indirectly implemented interface or a
+ * direct/indirect superclass.
+ */
+ public void findReferencingClasses2(ClassInfo classInfo1, ClassInfo classInfo2) {
+ Set<String> refClazz1 = new LinkedHashSet<String>();
+ findReferencingClasses(classInfo1, 2, false, refClazz1);
+ Set<String> refClazz2 = new LinkedHashSet<String>();
+ findReferencingClasses(classInfo2, 2, false, refClazz2);
+
+ for (String className1 : refClazz1) {
+ if (refClazz2.contains(className1)) {
+ addToAffectedClassNames(className1);
+ }
+ }
+ }
+
+ /** Find all project classes which are direct subclasses of the given class */
+ public void findDirectSubclasses(ClassInfo classInfo) {
+ for (ClassInfo subclassInfo : classInfo.getDirectSubclasses()) {
+ addToAffectedClassNames(subclassInfo.name);
+ }
+ }
+
+ /**
+ * Find all non-abstract project classes that implement the given interface or any of its
+ * subclasses directly, and all non-abstract classes that are direct descendants of abstract
+ * classes that implement the given interface directly or indirectly. Class C implements
+ * interface I indirectly, if C or some superclass of C directly implements I or some sublcass of I.
+ */
+ public void findDirectlyAndOtherwiseImplementingConcreteClasses(ClassInfo intfInfo) {
+ for (PCDEntry entry : pcdm.entries()) {
+ ClassInfo clientInfo =
+ pcdm.getClassInfoForPCDEntry(ClassInfo.VER_OLD, entry);
+ if (clientInfo == null) {
+ continue; // New class
+ }
+ if (clientInfo.isInterface()) {
+ continue;
+ }
+ if (clientInfo.isAbstract()) {
+ if (clientInfo.implementsInterfaceDirectlyOrIndirectly(intfInfo.name)) {
+ findAllNearestConcreteSubclasses(clientInfo);
+ }
+ } else {
+ if (clientInfo.implementsIntfOrSubintfDirectly(intfInfo.name)) {
+ addToAffectedClassNames(clientInfo.name);
+ }
+ }
+ }
+ }
+
+ private void findAllNearestConcreteSubclasses(ClassInfo classInfo) {
+ for (ClassInfo subclassInfo : classInfo.getDirectSubclasses()) {
+ if (subclassInfo.isAbstract()) {
+ findAllNearestConcreteSubclasses(subclassInfo);
+ } else {
+ addToAffectedClassNames(subclassInfo.name);
+ }
+ }
+ }
+
+ /**
+ * Find all interfaces and abstract classes that implement the given interface and declare or inherit
+ * a method with the given name. For those that overload this method, find referencing classes.
+ */
+ public void findAbstractSubtypesWithSameNameMethod(ClassInfo intfInfo, String mName, final String mSig) {
+ for (PCDEntry pcde : pcdm.entries()) {
+ ClassInfo ci =
+ pcdm.getClassInfoForPCDEntry(ClassInfo.VER_OLD, pcde);
+ if (ci == null) {
+ continue; // New class or not in project
+ }
+ if (!(ci.isInterface() || ci.isAbstract())) {
+ continue;
+ }
+ if (ci.implementsInterfaceDirectlyOrIndirectly(intfInfo.name)) {
+ addToAffectedClassNames(ci.name);
+ // Check if the new method overloads an existing (declared or inherited) method. Overloading test is rough -
+ // we just check if the number of parameters is the same.
+ ci.findExistingSameNameMethods(mName, true, true, new ClassInfo.MethodHandler() {
+
+ void handleMethod(ClassInfo classInfo, int otherMethodIdx) {
+ String otherMSig =
+ classInfo.methodSignatures[otherMethodIdx];
+ if ( (!mSig.equals(otherMSig)) &&
+ Utils.sameParamNumber(mSig, otherMSig)) {
+ findReferencingClassesForMethod(classInfo, otherMethodIdx);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ /** Find all project classes that reference the given field. */
+ public void findReferencingClassesForField(ClassInfo classInfo, int fieldNo) {
+ findReferencingClassesForMember(classInfo, fieldNo, true, false, false);
+ }
+
+ /**
+ * Find all project classes that reference the given field and which are in
+ * different packages.
+ */
+ public void findDiffPackageReferencingClassesForField(ClassInfo classInfo, int fieldNo) {
+ findReferencingClassesForMember(classInfo, fieldNo, true, true, false);
+ }
+
+ /**
+ * Find all project classes that reference the given field, which are in different
+ * packages and are direct or indirect subclasses of the member's declaring class
+ * (protected -&gt; default transformation).
+ */
+ public void findDiffPackageAndSubReferencingClassesForField(ClassInfo classInfo, int fieldNo) {
+ findReferencingClassesForMember(classInfo, fieldNo, true, true, true);
+ }
+
+ /** Find all project classes that reference the given method. */
+ public void findReferencingClassesForMethod(ClassInfo classInfo, int methodNo) {
+ findReferencingClassesForMember(classInfo, methodNo, false, false, false);
+ }
+
+ /**
+ * Find all project classes that reference the given method and which are in
+ * different packages.
+ */
+ public void findDiffPackageReferencingClassesForMethod(ClassInfo classInfo, int methodNo) {
+ findReferencingClassesForMember(classInfo, methodNo, false, true, false);
+ }
+
+ /**
+ * Find all project classes that reference the given method, which are in different
+ * packages and are direct or indirect subclasses of the member's declaring class
+ * (protected -&gt; default transformation)
+ */
+ public void findDiffPackageAndSubReferencingClassesForMethod(ClassInfo classInfo, int methodNo) {
+ findReferencingClassesForMember(classInfo, methodNo, false, true, true);
+ }
+
+ /**
+ * Find all project classes that re-implement the given method and that are
+ * direct/indirect subclasses of this method's declaring class. If some subclass C
+ * re-implements the given method, we don't have to search C's subclasses further.
+ */
+ public void findSubclassesReimplementingMethod(ClassInfo classInfo, int methodNo) {
+ findSubclassesReimplementingMethod(classInfo, classInfo, methodNo);
+ }
+
+ private void findSubclassesReimplementingMethod(ClassInfo targetClass, ClassInfo methodDeclaringClass, int methodNo) {
+ for (ClassInfo subclass : targetClass.getDirectSubclasses()) {
+ if (subclass.declaresMethod(methodDeclaringClass, methodNo)) {
+ addToAffectedClassNames(subclass.name);
+ } else {
+ findSubclassesReimplementingMethod(subclass, methodDeclaringClass, methodNo);
+ }
+ }
+ }
+
+ /**
+ * For a given class C, find all concrete direct subclasses, and all direct concrente subclasses of C's direct
+ * or indirect abstract subclasses.
+ */
+ public void findConcreteSubclasses(ClassInfo targetClass) {
+ for (ClassInfo subclass : targetClass.getDirectSubclasses()) {
+ if (subclass.isAbstract()) {
+ findConcreteSubclasses(subclass);
+ } else {
+ addToAffectedClassNames(subclass.name);
+ }
+ }
+ }
+
+ /**
+ * Find any concrete subclasses of targetClass that don't override or inherit a concrete implementation
+ * of the given method.
+ */
+ public void findConcreteSubclassesNotOverridingAbstractMethod(ClassInfo targetClass, ClassInfo methodDeclaringClass, int methodNo) {
+ for (ClassInfo subclass : targetClass.getDirectSubclasses()) {
+ int pos =
+ subclass.getDeclaredMethodPos(methodDeclaringClass, methodNo);
+ if (pos == -1) { // This method is not overridden in this class
+ if (!subclass.isAbstract()) {
+ addToAffectedClassNames(subclass.name);
+ } else {
+ findConcreteSubclassesNotOverridingAbstractMethod(subclass, methodDeclaringClass, methodNo);
+ }
+ } else { // A chance that this method is declared abstract once again...
+ if (Modifier.isAbstract(subclass.methodAccessFlags[pos])) {
+ findConcreteSubclassesNotOverridingAbstractMethod(subclass, methodDeclaringClass, methodNo);
+ }
+ }
+ }
+ }
+
+ /** Find all project classes that reference any method that throws the given exception. */
+ public void findRefsToMethodsThrowingException(ClassInfo excClassInfo) {
+ for (PCDEntry pcde : pcdm.entries()) {
+ ClassInfo classInfo =
+ pcdm.getClassInfoForPCDEntry(ClassInfo.VER_OLD, pcde);
+ if (classInfo == null) {
+ continue; // New class
+ }
+ int methodIdx = -1;
+ do {
+ methodIdx =
+ classInfo.hasMethodThrowingException(excClassInfo, methodIdx + 1);
+ if (methodIdx != -1) {
+ findReferencingClassesForMethod(classInfo, methodIdx);
+ }
+ } while (methodIdx != -1);
+ }
+ }
+
+ /**
+ * Find all project classes declaring a static field with the given name. Currently used only to look up
+ * classes referencing given class X via the "X.class" construct.
+ */
+ public void findClassesDeclaringField(String name, String signature, boolean isStatic, String packageToLookIn) {
+ for (PCDEntry pcde : pcdm.entries()) {
+ ClassInfo classInfo =
+ pcdm.getClassInfoForPCDEntry(ClassInfo.VER_OLD, pcde);
+ if (classInfo == null) {
+ continue; // New class
+ }
+ if (packageToLookIn != null &&
+ !classInfo.packageName.equals(packageToLookIn)) {
+ continue;
+ }
+ if (classInfo.declaresField(name, signature, isStatic)) {
+ addToAffectedClassNames(classInfo.name);
+ }
+ }
+ }
+
+ public void addToAffectedClassNames(String className) {
+ String res = pcdm.classAlreadyRecompiledOrUncompileable(className);
+ if (res == null) {
+ affectedClassNames.add(className);
+ } else if (!"".equals(res)) { // The dependent class comes from a .jar.
+ if (checkedClassIsFromJar || noWarnOnDependentJar) {
+ return;
+ }
+ String message = "Class " + className + " is affected by a change to " + checkedClassName + ", but can't be recompiled, " +
+ "since it is located in archive " + res;
+ if (failOnDependentJar) {
+ throw new PrivateException(new PublicExceptions.JarDependsOnSourceException(message));
+ } else {
+ Utils.printWarningMessage("Warning: " + message);
+ }
+ }
+ }
+
+ /**
+ * Find all project classes that reference the class with the given name.
+ * The second parameter controls the "thoroughness degree", and its value is passed to ClassInfo.referencesClass()
+ * method (see the comment to it). The fromDiffPackages parameter defines whether all such classes
+ * or only classes from different packages are required.
+ */
+ private void findReferencingClasses(ClassInfo classInfo,
+ int thorDegree, boolean fromDiffPackages,
+ Set<String> ret) {
+ String packageName = classInfo.packageName;
+ for (PCDEntry pcde : pcdm.entries()) {
+ ClassInfo clientInfo =
+ pcdm.getClassInfoForPCDEntry(ClassInfo.VER_OLD, pcde);
+ if (clientInfo == null) {
+ continue; // New class
+ }
+ if (fromDiffPackages && packageName.equals(clientInfo.packageName)) {
+ continue;
+ }
+ // If thorDegree == 2, i.e. indirect references from the constantpool (e.g. a reference to a method which
+ // has classInfo as one of its formal parameter types) are taken into account, then we should check all of
+ // the classes, whether classInfo is directly accessible from them or not.
+ if (thorDegree != 2 && (!classAccessibleFrom(classInfo, clientInfo))) {
+ continue;
+ }
+
+ if (clientInfo.referencesClass(classInfo.name, classInfo.isInterface(), thorDegree)) {
+ if (ret == null) {
+ addToAffectedClassNames(clientInfo.name);
+ } else {
+ ret.add(clientInfo.name);
+ }
+ }
+ }
+ }
+
+ /**
+ * Find all project classes that reference the given member. If fromDiffPackages
+ * is true, then only classes that do not belong to the package of the member's
+ * declaring class should be returned. If onlySubclasses is true, then only
+ * classes that are subclasses of member's declaring class should be returned.
+ */
+ private void findReferencingClassesForMember(ClassInfo declaringClassInfo, int memberNo,
+ boolean isField,
+ boolean fromDiffPackages, boolean onlySubclasses) {
+ String declaringClassName = declaringClassInfo.name;
+ String declaringClassPackage = declaringClassInfo.packageName;
+ for (PCDEntry pcde : pcdm.entries()) {
+ ClassInfo clientInfo =
+ pcdm.getClassInfoForPCDEntry(ClassInfo.VER_OLD, pcde);
+ if (clientInfo == null) {
+ continue; // New class
+ }
+ String className = clientInfo.name;
+ if (className.equals(declaringClassName)) {
+ continue;
+ }
+ if (!memberAccessibleFrom(declaringClassInfo, memberNo, clientInfo, isField)) {
+ continue;
+ }
+ if (fromDiffPackages &&
+ declaringClassPackage.equals(clientInfo.packageName)) {
+ continue;
+ }
+ if (onlySubclasses && !clientInfo.isSubclassOf(declaringClassName, false)) {
+ continue;
+ }
+
+ if (isField) {
+ if (clientInfo.referencesField(declaringClassInfo, memberNo)) {
+ addToAffectedClassNames(clientInfo.name);
+ }
+ } else {
+ if (clientInfo.referencesMethod(declaringClassInfo, memberNo)) {
+ addToAffectedClassNames(clientInfo.name);
+ }
+ }
+ }
+ }
+
+ /** Checks if class classInfo is accessible from class clientClassInfo. */
+ private boolean classAccessibleFrom(ClassInfo classInfo, ClassInfo clientClassInfo) {
+ char classFlags = classInfo.accessFlags;
+ String classPackage = classInfo.packageName;
+ String clientClassPackage = clientClassInfo.packageName;
+
+ if (Modifier.isPublic(classFlags)) {
+ return true;
+ } else if (Modifier.isProtected(classFlags)) {
+ if (classPackage.equals(clientClassPackage) ||
+ clientClassInfo.isSubclassOf(classInfo.directlyEnclosingClass, false)) {
+ return true;
+ }
+ } else if (Modifier.isPrivate(classFlags)) {
+ if (classInfo.topLevelEnclosingClass.equals(clientClassInfo.topLevelEnclosingClass)) {
+ return true;
+ }
+ } else {
+ if (classPackage.equals(clientClassPackage)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks if member memberNo (which is a field if isField == true, and method otherwise) of class memberClassInfo is
+ * accessible from class clientClassInfo.
+ */
+ private boolean memberAccessibleFrom(ClassInfo memberClassInfo,
+ int memberNo, ClassInfo clientClassInfo, boolean isField) {
+ char memberClassFlags = memberClassInfo.accessFlags;
+ char memberFlags = isField ? memberClassInfo.fieldAccessFlags[memberNo]
+ : memberClassInfo.methodAccessFlags[memberNo];
+ String memberClassPackage = memberClassInfo.packageName;
+ String clientClassPackage = clientClassInfo.packageName;
+
+ if (Modifier.isPublic(memberClassFlags)) {
+ if (Modifier.isPublic(memberFlags)) {
+ return true;
+ } else if (Modifier.isProtected(memberFlags) &&
+ (memberClassPackage.equals(clientClassPackage) ||
+ clientClassInfo.isSubclassOf(memberClassInfo.name, false))) {
+ return true;
+ } else if (Modifier.isPrivate(memberFlags)) {
+ if (memberClassInfo.topLevelEnclosingClass.equals(
+ clientClassInfo.topLevelEnclosingClass)) {
+ return true;
+ }
+ } else if (memberClassPackage.equals(clientClassPackage)) {
+ return true;
+ }
+ } else if (Modifier.isProtected(memberClassFlags)) {
+ if (!(memberClassPackage.equals(clientClassPackage) ||
+ clientClassInfo.isSubclassOf(memberClassInfo.directlyEnclosingClass, false))) {
+ return true;
+ }
+ if (Modifier.isPublic(memberFlags) ||
+ Modifier.isProtected(memberFlags)) {
+ return true;
+ } else if (Modifier.isPrivate(memberFlags)) {
+ if (memberClassInfo.topLevelEnclosingClass.equals(
+ clientClassInfo.topLevelEnclosingClass)) {
+ return true;
+ }
+ } else {
+ if (memberClassPackage.equals(clientClassPackage)) {
+ return true;
+ }
+ }
+ } else if (Modifier.isPrivate(memberClassFlags)) {
+ if (memberClassInfo.topLevelEnclosingClass.equals(
+ clientClassInfo.topLevelEnclosingClass)) {
+ return true;
+ }
+ } else { // memberClassInfo is package-private
+ if (!memberClassPackage.equals(clientClassPackage)) {
+ return false;
+ }
+ if (Modifier.isPublic(memberFlags) || Modifier.isProtected(memberFlags)) {
+ return true;
+ } else if (Modifier.isPrivate(memberFlags)) {
+ if (memberClassInfo.topLevelEnclosingClass.equals(
+ clientClassInfo.topLevelEnclosingClass)) {
+ return true;
+ }
+ } else {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/third_party/jmake/src/org/pantsbuild/jmake/TextProjectDatabaseReader.java b/third_party/jmake/src/org/pantsbuild/jmake/TextProjectDatabaseReader.java
new file mode 100644
index 0000000..d227a8e
--- /dev/null
+++ b/third_party/jmake/src/org/pantsbuild/jmake/TextProjectDatabaseReader.java
@@ -0,0 +1,107 @@
+/* Copyright (c) 2002-2013 Sun Microsystems, Inc. All rights reserved
+ *
+ * This program is distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+package org.pantsbuild.jmake;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.ObjectInputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+/**
+ * This class creates the internal representation of the project database from a text buffer.
+ *
+ * The Pants build tool manipulates this data in various ways, and it's easiest for it
+ * to do so by parsing text files directly. This brings JMake into line with Zinc (the
+ * Scala incremental compiler) and allows Pants to handle both uniformly.
+ *
+ * @author Benjy Weinberger
+ * 13 January 2013
+ */
+public class TextProjectDatabaseReader {
+ public Map<String,PCDEntry> readProjectDatabaseFromFile(File infile) {
+ try {
+ BufferedReader in =
+ new BufferedReader(new InputStreamReader(new FileInputStream(infile), "UTF-8"));
+ try {
+ return readProjectDatabase(in);
+ } finally {
+ in.close();
+ }
+ } catch (FileNotFoundException e) {
+ throw new PrivateException(e);
+ } catch (UnsupportedEncodingException e) {
+ throw new PrivateException(e);
+ } catch (IOException e) {
+ throw new PrivateException(e);
+ }
+ }
+
+ public Map<String,PCDEntry> readProjectDatabase(BufferedReader in) {
+ Map<String,PCDEntry> pcd;
+ try {
+ String line = in.readLine();
+ if (!"pcd entries:".equals(line))
+ throw error("Expected: 'pcd entries:', got: " + line);
+ line = in.readLine();
+ Matcher m = Pattern.compile("^(\\d+) items$").matcher(line);
+ if (!m.matches())
+ throw error("Expected: '<n> items', got: " + line);
+ int numEntries = Integer.parseInt(m.group(1));
+ pcd = new LinkedHashMap<String, PCDEntry>(numEntries);
+ for (int i = 0; i < numEntries; i++) {
+ line = in.readLine();
+ if (line == null)
+ throw error("Unexpected EOF");
+ String[] parts = line.split("\t");
+ if (parts.length != 5) {
+ throw error("Invalid line: " + line);
+ }
+ String className = parts[0];
+ String javaFullFilePath = parts[1];
+ long oldClassFileLastModified = Long.parseLong(parts[2]);
+ long oldClassFileFingerprint = Long.parseLong(parts[3]);
+ ClassInfo ci = classInfoFromBase64(parts[4]);
+ PCDEntry entry = new PCDEntry(className, javaFullFilePath, oldClassFileLastModified,
+ oldClassFileFingerprint, ci);
+ pcd.put(entry.className, entry);
+ }
+ // We're done: We have detailed dep information in the PCD entries, so we don't
+ // need to read the dep information lines from the file.
+ } catch (IOException e) {
+ throw new PrivateException(e);
+ }
+ return pcd;
+ }
+
+ private PrivateException error(String msg) {
+ return new PrivateException(new IllegalArgumentException(msg));
+ }
+
+ private ClassInfo classInfoFromBase64(String s) {
+ try {
+ byte[] bytes = Base64.decode(s.toCharArray());
+ ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
+ ClassInfo ret = (ClassInfo)ois.readObject();
+ ret.initializeImmediateTransientFields();
+ return ret;
+ } catch (IOException e) {
+ throw new PrivateException(e);
+ } catch (ClassNotFoundException e) {
+ throw new PrivateException(e);
+ }
+ }
+}
diff --git a/third_party/jmake/src/org/pantsbuild/jmake/TextProjectDatabaseWriter.java b/third_party/jmake/src/org/pantsbuild/jmake/TextProjectDatabaseWriter.java
new file mode 100644
index 0000000..ee5c08f
--- /dev/null
+++ b/third_party/jmake/src/org/pantsbuild/jmake/TextProjectDatabaseWriter.java
@@ -0,0 +1,144 @@
+/* Copyright (c) 2002-2013 Sun Microsystems, Inc. All rights reserved
+ *
+ * This program is distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+package org.pantsbuild.jmake;
+
+import java.io.BufferedWriter;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+
+/**
+ * This class implements writing a text stream representing a project database.
+ *
+ * @see TextProjectDatabaseReader for details.
+ *
+ * @author Benjy Weinberger
+ * 13 January 2013
+ */
+public class TextProjectDatabaseWriter {
+ private static Set<String> primitives = new LinkedHashSet<String>(
+ Arrays.asList("boolean", "byte", "char", "double", "float", "int", "long", "short",
+ "Z", "B", "C", "D", "F", "I", "J", "S"));
+
+ private ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Reusable temp buffer.
+
+ public void writeProjectDatabaseToFile(File outfile, Map<String, PCDEntry> pcd) {
+ try {
+ Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outfile), "UTF-8"));
+ try {
+ writeProjectDatabase(out, pcd);
+ } finally {
+ out.close();
+ }
+ } catch (FileNotFoundException e) {
+ throw new PrivateException(e);
+ } catch (UnsupportedEncodingException e) {
+ throw new PrivateException(e);
+ } catch (IOException e) {
+ throw new PrivateException(e);
+ }
+ }
+
+ public void writeProjectDatabase(Writer out, Map<String,PCDEntry> pcd) {
+ try {
+ out.write("pcd entries:\n");
+ out.write(Integer.toString(pcd.size()));
+ out.write(" items\n");
+ Map<String, Set<String>> depsBySource = new LinkedHashMap<String, Set<String>>();
+ for (PCDEntry entry : pcd.values()) {
+ writePCDEntry(out, entry);
+ Set<String> deps = depsBySource.get(entry.javaFileFullPath);
+ if (deps == null) {
+ deps = new LinkedHashSet<String>();
+ depsBySource.put(entry.javaFileFullPath, deps);
+ }
+ addDepsFromClassInfo(deps, entry.oldClassInfo);
+ }
+ // Write out dependency information. Note that we don't need to read this back to recreate
+ // the PCD. We write it out here just as a convenience, so that external readers of the PDB
+ // file don't have to grok our internal ClassInfo structures.
+ out.write("dependencies:\n");
+ out.write(Integer.toString(depsBySource.size()));
+ out.write(" items\n");
+ for (Map.Entry<String, Set<String>> item : depsBySource.entrySet()) {
+ out.write(item.getKey());
+ for (String s : item.getValue()) {
+ out.write('\t');
+ out.write(s);
+ }
+ out.write('\n');
+ }
+ } catch (IOException e) {
+ throw new PrivateException(e);
+ }
+ }
+
+ private void addDepsFromClassInfo(Set<String> deps, ClassInfo ci) {
+ for (String s : ci.cpoolRefsToClasses) {
+ int i = 0;
+ int j = s.length();
+
+ // Fix some inconsistencies in how we represent types internally:
+ // Despite the comment on ci.cpoolRefsToClasses, class names may be
+ // representing in it with '['s and with '@', '#' instead of 'L', ';'.
+ while (s.charAt(i) == '[') i++;
+ if (s.charAt(i) == '@') i++;
+ if (s.endsWith("#")) j--;
+ int k = s.indexOf('$');
+
+ // Take the outer class, on references to nested classes.
+ if (k != -1) j = k;
+ if (i > 0 || j < s.length())
+ s = s.substring(i, j);
+
+ // We don't need to record deps on primitive types, or arrays of them.
+ if (!primitives.contains(s))
+ deps.add(s);
+ }
+ }
+
+ private void writePCDEntry(Writer out, PCDEntry entry) {
+ try {
+ out.write(entry.className);
+ out.write('\t');
+ out.write(entry.javaFileFullPath);
+ out.write('\t');
+ out.write(Long.toString(entry.oldClassFileLastModified));
+ out.write('\t');
+ out.write(Long.toString(entry.oldClassFileFingerprint));
+ out.write('\t');
+ out.write(classInfoToBase64(entry.oldClassInfo));
+ out.write('\n');
+ } catch (IOException e) {
+ throw new PrivateException(e);
+ }
+ }
+
+ private char[] classInfoToBase64(ClassInfo ci) {
+ baos.reset();
+ try {
+ ObjectOutputStream oos = new ObjectOutputStream(baos);
+ oos.writeObject(ci);
+ oos.close();
+ } catch (IOException e) {
+ throw new PrivateException(e);
+ }
+ return Base64.encode(baos.toByteArray());
+ }
+}
diff --git a/third_party/jmake/src/org/pantsbuild/jmake/Utils.java b/third_party/jmake/src/org/pantsbuild/jmake/Utils.java
new file mode 100644
index 0000000..38566a9
--- /dev/null
+++ b/third_party/jmake/src/org/pantsbuild/jmake/Utils.java
@@ -0,0 +1,355 @@
+/* Copyright (c) 2002-2008 Sun Microsystems, Inc. All rights reserved
+ *
+ * This program is distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+package org.pantsbuild.jmake;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * Utility functions used by other classes from this package.
+ *
+ * @author Misha Dmitriev
+ * 23 January 2003
+ */
+public class Utils {
+
+ static final String REPORT_PROBLEM =
+ "Please report this problem to Mikhail.Dmitriev@sun.com";
+ static final byte[] MAGIC = {'J', 'a', 'v', 'a', 'm', 'a', 'k', 'e', ' ', 'P', 'r', 'o', 'j', 'e', 'c', 't', ' ', 'D', 'a', 't', 'a', 'b', 'a', 's', 'e', ' ', 'F', 'i', 'l', 'e'};
+ static final int magicLength = MAGIC.length;
+ static final int PDB_FORMAT_CODE_OLD = 1;
+ static final int PDB_FORMAT_CODE_133 = 0x01030300;
+ static final int PDB_FORMAT_CODE_LATEST = PDB_FORMAT_CODE_133;
+ static final int JAVAC_TARGET_RELEASE_OLDEST = 0x01040000; // 1.4 and previous versions
+ static final int JAVAC_TARGET_RELEASE_15 = 0x01050000; // if class is compiled with -target 1.5
+ static final int JAVAC_TARGET_RELEASE_16 = 0x01060000; // if class is compiled with -target 1.6
+ static final int JAVAC_TARGET_RELEASE_17 = 0x01070000; // if class is compiled with -target 1.7
+ static final int JAVAC_TARGET_RELEASE_18 = 0x01080000; // if class is compiled with -target 1.8
+ static int warningLimit = 20; // Maximum number of warnings to print
+ static final int TIMING_TOTAL = 0;
+ static final int TIMING_PDBREAD = 1;
+ static final int TIMING_SYNCHRO = 2;
+ static final int TIMING_SYNCHRO_CHECK_JAVA_FILES = 3;
+ static final int TIMING_FIND_UPDATED_JAVA_FILES = 4;
+ static final int TIMING_CLASS_FILE_OBSOLETE_OR_DELETED = 5;
+ static final int TIMING_COMPILE = 6;
+ static final int TIMING_FIND_UPDATED_CLASSES = 7;
+ static final int TIMING_CHECK_UPDATED_CLASSES = 8;
+ static final int TIMING_PDBWRITE = 9;
+ static final int TIMING_SYNCHRO_CHECK_TMP = 10;
+ static final int TIMING_CLASS_FILE_OBSOLETE_TMP = 11;
+ static final int TIMING_PDBUPDATE = 12;
+ static final int TIMING_ARRAY_LENGTH = 13;
+ private static long timings[] = new long[TIMING_ARRAY_LENGTH];
+ private static boolean timingOn = false;
+
+
+ // -------------------------------------------------------------------------------
+ // Name manipulation stuff
+ // -------------------------------------------------------------------------------
+ /**
+ * Returns package name for the given class. In case of no package, returns an
+ * empty, but non-null string. Returned string is interned.
+ */
+ public static String getPackageName(String clazzName) {
+ int ldi = clazzName.lastIndexOf('/'); // For convenience, we use system-internal slashes, not dots
+ if (ldi == -1) {
+ return "";
+ } else {
+ return clazzName.substring(0, ldi).intern();
+ }
+ }
+
+ /**
+ * Returns directly enclosing class name for the given class. If the given class is not a
+ * nested class, returns empty, but non-null string. Returned string is interned.
+ * NOTE FOR JDK 1.5: this function has to work with both old (1.4 and before) and new (1.5) ways
+ * of naming non-member classes. javacTargetRelease determines the javac version for this class;
+ * however on rare occasions (when checking a deleted non-project class) it may be 0, denoting
+ * that javac version is not known.
+ * In that case, we use the old algorithm, which is error-prone due to a bug in nested class
+ * naming that existed prior to JDK 1.5, where both a non-member local nested class B of A, and a
+ * member nested class B of anonymous class A$1, are named A$1$B.
+ */
+ public static String getDirectlyEnclosingClass(String clazzName, int javacTargetRelease) {
+ int ldi = clazzName.lastIndexOf('$');
+ if (ldi == -1) {
+ return "";
+ }
+
+ if (javacTargetRelease >= JAVAC_TARGET_RELEASE_15) {
+ return clazzName.substring(0, ldi).intern();
+ } else { // JAVAC_TARGET_RELEASE_OLDEST or unknown
+ // Take into account local classes which are named like "EncClass$1$LocalClass", where EncClass
+ // is directly enclosing class.
+ int lldi = clazzName.lastIndexOf('$', ldi - 1);
+ if (lldi == -1 || !Character.isDigit(clazzName.charAt(lldi + 1))) {
+ return clazzName.substring(0, ldi).intern();
+ } else {
+ return clazzName.substring(0, lldi).intern();
+ }
+ }
+ }
+
+ /**
+ * Returns top-level enclosing class name for the given class. If the given class is not a
+ * nested class, returns empty, but non-null string. Returned string is interned.
+ */
+ public static String getTopLevelEnclosingClass(String clazzName) {
+ int fdi = clazzName.indexOf('$');
+ if (fdi == -1) {
+ return "";
+ }
+
+ return clazzName.substring(0, fdi).intern();
+ }
+
+ /**
+ * Given the full path for the enclosing class file and the full name for the nested class, return the supposed
+ * full path for the nested class.
+ */
+ public static String getClassFileFullPathForNestedClass(String enclosingClassFileFullPath, String nestedClassFullName) {
+ String enclosingClassDir = enclosingClassFileFullPath;
+ int cutIndex = enclosingClassDir.lastIndexOf(File.separatorChar);
+ enclosingClassDir = enclosingClassDir.substring(0, cutIndex + 1); // If slash is present, it's included, otherwise we get ""
+ cutIndex = nestedClassFullName.lastIndexOf('/');
+ String nestedClassLocalName;
+ if (cutIndex < 0) {
+ nestedClassLocalName = nestedClassFullName;
+ } else {
+ nestedClassLocalName = nestedClassFullName.substring(cutIndex + 1);
+ }
+ return enclosingClassDir + nestedClassLocalName + ".class";
+ }
+
+ /**
+ * For two strings representing signatures, check if the number of parameters in
+ * both is the same.
+ */
+ public static boolean sameParamNumber(String sig1, String sig2) {
+ return getParamNumber(sig1) == getParamNumber(sig2);
+ }
+
+ private static int getParamNumber(String sig) {
+ char ch;
+ int parNo = 0, pos = 0;
+ do {
+ ch = sig.charAt(++pos);
+ if (ch == ')') {
+ break;
+ }
+ while (ch == '[') {
+ ch = sig.charAt(++pos);
+ }
+ parNo++;
+ if (ch == '@') {
+ // We replaced all "Lclassname;" in signatures with "@classname#"
+ while (ch != '#') {
+ ch = sig.charAt(++pos);
+ }
+ }
+ } while (ch != ')');
+ return parNo;
+ }
+
+
+ // -------------------------------------------------------------------------------
+ // File related stuff
+ // -------------------------------------------------------------------------------
+ public static File checkFileForName(String name) {
+ // For each .java file, a File object is created two times when jmake executes: first when we synchronise the PCD
+ // and the supplied .java file list (we make sure that the .java file exists), and second time when we check if a class
+ // file was updated (we compare time stamps of the .java and the .class file). I tried to call this routine for a .java
+ // class both times, and cached File objects, but it looks as if this does not bring any real speed-up (and in fact may
+ // even slow down the application). Most of the time seems to go to the underlying code creating internal File
+ // representation; once it is created, it takes little time to execute another "new File()" for it. Also, all operations
+ // on files like getCanonicalPath() or lastModified() seem to be quite expensive, so their unnecessary repetition should
+ // be avoided as much as possible.
+ if (name == null) {
+ return null;
+ }
+ File file = new File(name);
+ if (file.exists()) {
+ return file;
+ }
+ return null;
+ }
+
+ public static File checkOrCreateDirForName(String name) {
+ File file = new File(name);
+ if (!file.exists()) {
+ file.mkdirs();
+ }
+ if (file.exists()) {
+ if (!file.isDirectory()) {
+ throw new PrivateException(new PublicExceptions.InternalException(file + " is not a directory."));
+ }
+ return file;
+ }
+ return null;
+ }
+
+ public static byte[] readFileIntoBuffer(File file) {
+ try {
+ InputStream in = new FileInputStream(file);
+ int len = (int) file.length();
+ return readInputStreamIntoBuffer(in, len);
+ } catch (IOException e) {
+ throw new PrivateException(e);
+ }
+ }
+
+ public static byte[] readZipEntryIntoBuffer(ZipFile file, ZipEntry entry) {
+ try {
+ InputStream in = file.getInputStream(entry);
+ int len = (int) entry.getSize();
+ return Utils.readInputStreamIntoBuffer(in, len);
+ } catch (IOException e) {
+ throw new PrivateException(e);
+ }
+ }
+
+ public static byte[] readInputStreamIntoBuffer(InputStream in, int len) throws IOException {
+ byte buf[] = new byte[len];
+ int readBytes, ofs = 0, remBytes = len;
+ do {
+ readBytes = in.read(buf, ofs, remBytes);
+ ofs += readBytes;
+ remBytes -= readBytes;
+ } while (ofs < len);
+ in.close();
+ return buf;
+ }
+
+ public static void readAndPrintBytesFromStream(InputStream in, OutputStream out) throws IOException {
+ int avail = in.available();
+ if (avail > 0) {
+ byte outbytes[] = new byte[avail];
+ int realOutBytes = in.read(outbytes);
+ out.write(outbytes, 0, realOutBytes);
+ }
+ }
+
+ /** For a Windows path, convert the drive letter to the lower case */
+ public static String convertDriveLetterToLowerCase(String path) {
+ if (path.charAt(1) != ':') {
+ return path;
+ }
+ char drive = path.charAt(0);
+ if (Character.isUpperCase(drive)) {
+ drive = Character.toLowerCase(drive);
+ char[] chars = path.toCharArray();
+ chars[0] = drive;
+ path = new String(chars);
+ }
+ return path;
+ }
+
+ public static void ignore(Exception e) {
+ // Ignore this exception
+ }
+
+ /** Used when invoking a third-party executable compiler app */
+ public static void delay(int ms) {
+ Object o = new Object();
+ synchronized (o) {
+ try {
+ o.wait(ms);
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ // -------------------------------------------------------------------------------
+ // Custom printing stuff
+ // -------------------------------------------------------------------------------
+ private static PrintStream out = System.out;
+ private static PrintStream warn = System.out;
+ private static PrintStream err = System.err;
+ private static boolean printInfoMessages = true;
+ private static boolean printWarningMessages = true;
+ private static boolean printErrorMessages = true;
+ private static int warningNo;
+
+ public static void setOutputStreams(PrintStream out, PrintStream warn, PrintStream err) {
+ Utils.out = out;
+ Utils.warn = warn;
+ Utils.err = err;
+ }
+
+ public static void customizeOutput(boolean printInfoMessages, boolean printWarningMessages, boolean printErrorMessages) {
+ Utils.printInfoMessages = printInfoMessages;
+ Utils.printWarningMessages = printWarningMessages;
+ Utils.printErrorMessages = printErrorMessages;
+ }
+
+ public static void printInfoMessage(String message) {
+ if (printInfoMessages) {
+ out.println(message);
+ }
+ }
+
+ public static void printInfoMessageNoEOL(String message) {
+ if (printInfoMessages) {
+ out.print(message);
+ }
+ }
+
+ public static void printWarningMessage(String message) {
+ if (!printWarningMessages) {
+ return;
+ }
+ if (warningNo < warningLimit) {
+ warn.println(message);
+ } else if (warningNo == warningLimit) {
+ warn.println("jmake: more than " + warningLimit + " warnings.");
+ }
+ warningNo++;
+ }
+
+ public static void printErrorMessage(String message) {
+ if (printErrorMessages) {
+ err.println("jmake: " + message);
+ }
+ }
+
+ // -------------------------------------------------------------------------------
+ // Measuring stuff
+ // -------------------------------------------------------------------------------
+ public static void setTimingOn() {
+ timingOn = true;
+ }
+
+ public static void startTiming(int slot) {
+ timings[slot] = System.currentTimeMillis();
+ }
+
+ public static void stopAndPrintTiming(String message, int slot) {
+ if (timingOn) {
+ long time = System.currentTimeMillis() - timings[slot];
+ printInfoMessage("========== " + message + " time = " + time);
+ }
+ }
+
+ public static void printTiming(String message, int slot) {
+ if (timingOn) {
+ printInfoMessage("========== " + message + " time = " + timings[slot]);
+ }
+ }
+
+ public static void stopAndAddTiming(int slot1, int slot2) {
+ if (timingOn) {
+ long time = System.currentTimeMillis() - timings[slot1];
+ timings[slot2] += time;
+ }
+ }
+}