diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-12-10 16:19:40 +0100 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-12-10 16:01:50 +0000 |
commit | 51f6c2793adab2d864b3d2b360000ef8db1d3e92 (patch) | |
tree | 835b3b4446b012c75e80177cef9fbe6972cc7dbe /chromium/third_party/jmake | |
parent | 6036726eb981b6c4b42047513b9d3f4ac865daac (diff) | |
download | qtwebengine-chromium-51f6c2793adab2d864b3d2b360000ef8db1d3e92.tar.gz |
BASELINE: Update Chromium to 71.0.3578.93
Change-Id: I6a32086c33670e1b033f8b10e6bf1fd4da1d105d
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
Diffstat (limited to 'chromium/third_party/jmake')
19 files changed, 7495 insertions, 0 deletions
diff --git a/chromium/third_party/jmake/src/org/pantsbuild/jmake/Base64.java b/chromium/third_party/jmake/src/org/pantsbuild/jmake/Base64.java new file mode 100644 index 00000000000..af02ca6d7d8 --- /dev/null +++ b/chromium/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/chromium/third_party/jmake/src/org/pantsbuild/jmake/BinaryFileReader.java b/chromium/third_party/jmake/src/org/pantsbuild/jmake/BinaryFileReader.java new file mode 100644 index 00000000000..6e2cb4ef42c --- /dev/null +++ b/chromium/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/chromium/third_party/jmake/src/org/pantsbuild/jmake/BinaryFileWriter.java b/chromium/third_party/jmake/src/org/pantsbuild/jmake/BinaryFileWriter.java new file mode 100644 index 00000000000..01de41589e1 --- /dev/null +++ b/chromium/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/chromium/third_party/jmake/src/org/pantsbuild/jmake/BinaryProjectDatabaseReader.java b/chromium/third_party/jmake/src/org/pantsbuild/jmake/BinaryProjectDatabaseReader.java new file mode 100644 index 00000000000..e75e2064181 --- /dev/null +++ b/chromium/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/chromium/third_party/jmake/src/org/pantsbuild/jmake/BinaryProjectDatabaseWriter.java b/chromium/third_party/jmake/src/org/pantsbuild/jmake/BinaryProjectDatabaseWriter.java new file mode 100644 index 00000000000..fafaa398153 --- /dev/null +++ b/chromium/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/chromium/third_party/jmake/src/org/pantsbuild/jmake/ClassFileReader.java b/chromium/third_party/jmake/src/org/pantsbuild/jmake/ClassFileReader.java new file mode 100644 index 00000000000..3ece22d9c33 --- /dev/null +++ b/chromium/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/chromium/third_party/jmake/src/org/pantsbuild/jmake/ClassInfo.java b/chromium/third_party/jmake/src/org/pantsbuild/jmake/ClassInfo.java new file mode 100644 index 00000000000..9bfcd5a0b21 --- /dev/null +++ b/chromium/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/chromium/third_party/jmake/src/org/pantsbuild/jmake/ClassPath.java b/chromium/third_party/jmake/src/org/pantsbuild/jmake/ClassPath.java new file mode 100644 index 00000000000..e6e1aebd86a --- /dev/null +++ b/chromium/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/chromium/third_party/jmake/src/org/pantsbuild/jmake/CompatibilityChecker.java b/chromium/third_party/jmake/src/org/pantsbuild/jmake/CompatibilityChecker.java new file mode 100644 index 00000000000..bd74cfb3e0a --- /dev/null +++ b/chromium/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/chromium/third_party/jmake/src/org/pantsbuild/jmake/Main.java b/chromium/third_party/jmake/src/org/pantsbuild/jmake/Main.java new file mode 100644 index 00000000000..0e8055489fb --- /dev/null +++ b/chromium/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/chromium/third_party/jmake/src/org/pantsbuild/jmake/PCDContainer.java b/chromium/third_party/jmake/src/org/pantsbuild/jmake/PCDContainer.java new file mode 100644 index 00000000000..b02cfa49fee --- /dev/null +++ b/chromium/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/chromium/third_party/jmake/src/org/pantsbuild/jmake/PCDEntry.java b/chromium/third_party/jmake/src/org/pantsbuild/jmake/PCDEntry.java new file mode 100644 index 00000000000..72fb3a47f77 --- /dev/null +++ b/chromium/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/chromium/third_party/jmake/src/org/pantsbuild/jmake/PCDManager.java b/chromium/third_party/jmake/src/org/pantsbuild/jmake/PCDManager.java new file mode 100644 index 00000000000..5ff3cd10a95 --- /dev/null +++ b/chromium/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/chromium/third_party/jmake/src/org/pantsbuild/jmake/PrivateException.java b/chromium/third_party/jmake/src/org/pantsbuild/jmake/PrivateException.java new file mode 100644 index 00000000000..b8b0c78984f --- /dev/null +++ b/chromium/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/chromium/third_party/jmake/src/org/pantsbuild/jmake/PublicExceptions.java b/chromium/third_party/jmake/src/org/pantsbuild/jmake/PublicExceptions.java new file mode 100644 index 00000000000..40097454d0c --- /dev/null +++ b/chromium/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/chromium/third_party/jmake/src/org/pantsbuild/jmake/RefClassFinder.java b/chromium/third_party/jmake/src/org/pantsbuild/jmake/RefClassFinder.java new file mode 100644 index 00000000000..58c8884ab55 --- /dev/null +++ b/chromium/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 -> 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 -> 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 -> 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 -> 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 -> 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 -> 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 -> 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 -> 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/chromium/third_party/jmake/src/org/pantsbuild/jmake/TextProjectDatabaseReader.java b/chromium/third_party/jmake/src/org/pantsbuild/jmake/TextProjectDatabaseReader.java new file mode 100644 index 00000000000..d227a8eba8e --- /dev/null +++ b/chromium/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/chromium/third_party/jmake/src/org/pantsbuild/jmake/TextProjectDatabaseWriter.java b/chromium/third_party/jmake/src/org/pantsbuild/jmake/TextProjectDatabaseWriter.java new file mode 100644 index 00000000000..ee5c08f5dba --- /dev/null +++ b/chromium/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/chromium/third_party/jmake/src/org/pantsbuild/jmake/Utils.java b/chromium/third_party/jmake/src/org/pantsbuild/jmake/Utils.java new file mode 100644 index 00000000000..38566a97784 --- /dev/null +++ b/chromium/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; + } + } +} |