001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.harmony.pack200;
018:
019: import java.io.BufferedInputStream;
020: import java.io.DataOutputStream;
021: import java.io.IOException;
022: import java.io.InputStream;
023: import java.io.OutputStream;
024: import java.util.ArrayList;
025: import java.util.Iterator;
026: import java.util.List;
027: import java.util.jar.JarEntry;
028: import java.util.jar.JarOutputStream;
029: import java.util.zip.GZIPInputStream;
030:
031: import org.apache.harmony.pack200.bytecode.Attribute;
032: import org.apache.harmony.pack200.bytecode.CPClass;
033: import org.apache.harmony.pack200.bytecode.CPField;
034: import org.apache.harmony.pack200.bytecode.CPMethod;
035: import org.apache.harmony.pack200.bytecode.CPUTF8;
036: import org.apache.harmony.pack200.bytecode.ClassConstantPool;
037: import org.apache.harmony.pack200.bytecode.ClassFile;
038: import org.apache.harmony.pack200.bytecode.ClassFileEntry;
039: import org.apache.harmony.pack200.bytecode.InnerClassesAttribute;
040: import org.apache.harmony.pack200.bytecode.SourceFileAttribute;
041:
042: /**
043: * A Pack200 archive consists of one (or more) segments. Each segment is
044: * standalone, in the sense that every segment has the magic number header;
045: * thus, every segment is also a valid archive. However, it is possible to
046: * combine (non-GZipped) archives into a single large archive by concatenation
047: * alone. Thus all the hard work in unpacking an archive falls to understanding
048: * a segment.
049: *
050: * This class implements the Pack200 specification by an entry point ({@link #parse(InputStream)})
051: * which in turn delegates to a variety of other parse methods. Each parse
052: * method corresponds (roughly) to the name of the bands in the Pack200
053: * specification.
054: *
055: * The first component of a segment is the header; this contains (amongst other
056: * things) the expected counts of constant pool entries, which in turn defines
057: * how many values need to be read from the stream. Because values are variable
058: * width (see {@link Codec}), it is not possible to calculate the start of the
059: * next segment, although one of the header values does hint at the size of the
060: * segment if non-zero, which can be used for buffering purposes.
061: *
062: * Note that this does not perform any buffering of the input stream; each value
063: * will be read on a byte-by-byte basis. It does not perform GZip decompression
064: * automatically; both of these are expected to be done by the caller if the
065: * stream has the magic header for GZip streams ({@link GZIPInputStream#GZIP_MAGIC}).
066: * In any case, if GZip decompression is being performed the input stream will
067: * be buffered at a higher level, and thus this can read on a byte-oriented
068: * basis.
069: */
070: public class Segment {
071:
072: /**
073: * TODO: Do we need this method now we have Archive as the main entry point?
074: *
075: * Decode a segment from the given input stream. This does not attempt to
076: * re-assemble or export any class files, but it contains enough information
077: * to be able to re-assemble class files by external callers.
078: *
079: * @param in
080: * the input stream to read from
081: * @return a segment parsed from the input stream
082: * @throws IOException
083: * if a problem occurs during reading from the underlying stream
084: * @throws Pack200Exception
085: * if a problem occurs with an unexpected value or unsupported
086: * codec
087: */
088: public static Segment parse(InputStream in) throws IOException,
089: Pack200Exception {
090: Segment segment = new Segment();
091: segment.parseSegment(in);
092: return segment;
093: }
094:
095: private SegmentHeader header;
096:
097: private CpBands cpBands;
098:
099: private AttrDefinitionBands attrDefinitionBands;
100:
101: private IcBands icBands;
102:
103: private ClassBands classBands;
104:
105: private BcBands bcBands;
106:
107: private FileBands fileBands;
108:
109: private boolean overrideDeflateHint;
110:
111: private boolean deflateHint;
112:
113: private ClassFile buildClassFile(int classNum)
114: throws Pack200Exception {
115: ClassFile classFile = new ClassFile();
116: classFile.major = header.getDefaultClassMajorVersion(); // TODO If
117: // classVersionMajor[] use
118: // that instead
119: classFile.minor = header.getDefaultClassMinorVersion(); // TODO if
120: // classVersionMinor[] use
121: // that instead
122: // build constant pool
123: ClassConstantPool cp = classFile.pool;
124: String fullName = classBands.getClassThis()[classNum];
125: // SourceFile attribute
126: int i = fullName.lastIndexOf("/") + 1; // if lastIndexOf==-1, then
127: // -1+1=0, so str.substring(0)
128: // == str
129: AttributeLayout SOURCE_FILE = attrDefinitionBands
130: .getAttributeDefinitionMap().getAttributeLayout(
131: AttributeLayout.ATTRIBUTE_SOURCE_FILE,
132: AttributeLayout.CONTEXT_CLASS);
133: if (SOURCE_FILE.matches(classBands.getClassFlags()[classNum])) {
134: int firstDollar = SegmentUtils.indexOfFirstDollar(fullName);
135: String fileName = null;
136:
137: if (firstDollar > -1 && (i <= firstDollar)) {
138: fileName = fullName.substring(i, firstDollar) + ".java";
139: } else {
140: fileName = fullName.substring(i) + ".java";
141: }
142: classFile.attributes = new Attribute[] { (Attribute) cp
143: .add(new SourceFileAttribute(fileName)) };
144: } else {
145: classFile.attributes = new Attribute[] {};
146: }
147: // this/superclass
148: ClassFileEntry cfThis = cp.add(new CPClass(fullName));
149: ClassFileEntry cfSuper = cp.add(new CPClass(classBands
150: .getClassSuper()[classNum]));
151: // add interfaces
152: ClassFileEntry cfInterfaces[] = new ClassFileEntry[classBands
153: .getClassInterfaces()[classNum].length];
154: for (i = 0; i < cfInterfaces.length; i++) {
155: cfInterfaces[i] = cp.add(new CPClass(classBands
156: .getClassInterfaces()[classNum][i]));
157: }
158: // add fields
159: ClassFileEntry cfFields[] = new ClassFileEntry[classBands
160: .getClassFieldCount()[classNum]];
161: // fieldDescr and fieldFlags used to create this
162: for (i = 0; i < cfFields.length; i++) {
163: cfFields[i] = cp.add(new CPField(
164: classBands.getFieldDescr()[classNum][i], classBands
165: .getFieldFlags()[classNum][i], classBands
166: .getFieldAttributes()[classNum][i]));
167: }
168: // add methods
169: ClassFileEntry cfMethods[] = new ClassFileEntry[classBands
170: .getClassMethodCount()[classNum]];
171: // fieldDescr and fieldFlags used to create this
172: for (i = 0; i < cfMethods.length; i++) {
173: cfMethods[i] = cp.add(new CPMethod(classBands
174: .getMethodDescr()[classNum][i], classBands
175: .getMethodFlags()[classNum][i], classBands
176: .getMethodAttributes()[classNum][i]));
177: }
178:
179: // add inner class attribute (if required)
180: boolean addInnerClassesAttr = false;
181: IcTuple[] ic_local = getClassBands().getIcLocal()[classNum];
182: boolean ic_local_sent = false;
183: if (ic_local != null) {
184: ic_local_sent = true;
185: }
186: InnerClassesAttribute innerClassesAttribute = new InnerClassesAttribute(
187: "InnerClasses");
188: IcTuple[] ic_relevant = getIcBands().getRelevantIcTuples(
189: fullName, cp);
190: IcTuple[] ic_stored = computeIcStored(ic_local, ic_relevant);
191: for (int index = 0; index < ic_stored.length; index++) {
192: String innerClassString = ic_stored[index]
193: .this ClassString();
194: String outerClassString = ic_stored[index]
195: .outerClassString();
196: String simpleClassName = ic_stored[index].simpleClassName();
197:
198: CPClass innerClass = null;
199: CPUTF8 innerName = null;
200: CPClass outerClass = null;
201:
202: if (ic_stored[index].isAnonymous()) {
203: innerClass = new CPClass(innerClassString);
204: } else {
205: innerClass = new CPClass(innerClassString);
206: innerName = new CPUTF8(simpleClassName,
207: ClassConstantPool.DOMAIN_ATTRIBUTEASCIIZ);
208: }
209:
210: if (ic_stored[index].isMember()) {
211: outerClass = new CPClass(outerClassString);
212: }
213:
214: int flags = ic_stored[index].F;
215: innerClassesAttribute.addInnerClassesEntry(innerClass,
216: outerClass, innerName, flags);
217: addInnerClassesAttr = true;
218: }
219: // If ic_local is sent and it's empty, don't add
220: // the inner classes attribute.
221: if (ic_local_sent && (ic_local.length == 0)) {
222: addInnerClassesAttr = false;
223: }
224:
225: // If ic_local is not sent and ic_relevant is empty,
226: // don't add the inner class attribute.
227: if (!ic_local_sent && (ic_relevant.length == 0)) {
228: addInnerClassesAttr = false;
229: }
230:
231: if (addInnerClassesAttr) {
232: // Need to add the InnerClasses attribute to the
233: // existing classFile attributes.
234: Attribute[] originalAttrs = classFile.attributes;
235: Attribute[] newAttrs = new Attribute[originalAttrs.length + 1];
236: for (int index = 0; index < originalAttrs.length; index++) {
237: newAttrs[index] = originalAttrs[index];
238: }
239: newAttrs[newAttrs.length - 1] = innerClassesAttribute;
240: classFile.attributes = newAttrs;
241: cp.add(innerClassesAttribute);
242: }
243: // sort CP according to cp_All
244: cp.resolve(this );
245: // print out entries
246: debug("Constant pool looks like:");
247: for (i = 1; i <= cp.size(); i++) {
248: debug(String.valueOf(i) + ":" + String.valueOf(cp.get(i)));
249: }
250: // NOTE the indexOf is only valid after the cp.resolve()
251: // build up remainder of file
252: classFile.accessFlags = (int) classBands.getClassFlags()[classNum];
253: classFile.this Class = cp.indexOf(cfThis);
254: classFile.super Class = cp.indexOf(cfSuper);
255: // TODO placate format of file for writing purposes
256: classFile.interfaces = new int[cfInterfaces.length];
257: for (i = 0; i < cfInterfaces.length; i++) {
258: classFile.interfaces[i] = cp.indexOf(cfInterfaces[i]);
259: }
260: classFile.fields = cfFields;
261: classFile.methods = cfMethods;
262: return classFile;
263: }
264:
265: /**
266: * Given an ic_local and an ic_relevant, use them to
267: * calculate what should be added as ic_stored.
268: * @param ic_local IcTuple[] array of local transmitted tuples
269: * @param ic_relevant IcTuple[] array of relevant tuples
270: * @return IcTuple[] array of tuples to be stored. If ic_local
271: * is null or empty, the values returned may not be correct.
272: * The caller will have to determine if this is the case.
273: */
274: private IcTuple[] computeIcStored(IcTuple[] ic_local,
275: IcTuple[] ic_relevant) {
276: List result = new ArrayList();
277: List resultCopy = new ArrayList();
278: List localList = new ArrayList();
279: List relevantList = new ArrayList();
280: if (ic_local != null) {
281: // If ic_local is null, this code doesn't get
282: // executed - which means the list ends up being
283: // ic_relevant.
284: for (int index = 0; index < ic_local.length; index++) {
285: result.add(ic_local[index]);
286: resultCopy.add(ic_local[index]);
287: localList.add(ic_local[index]);
288: }
289: }
290: for (int index = 0; index < ic_relevant.length; index++) {
291: result.add(ic_relevant[index]);
292: resultCopy.add(ic_relevant[index]);
293: relevantList.add(ic_relevant[index]);
294: }
295:
296: // Since we're removing while iterating, iterate over
297: // a copy.
298: Iterator it = resultCopy.iterator();
299:
300: while (it.hasNext()) {
301: IcTuple tuple = (IcTuple) it.next();
302: if (localList.contains(tuple)
303: && relevantList.contains(tuple)) {
304: while (result.remove(tuple)) {
305: }
306: ;
307: }
308: }
309: IcTuple[] resultArray = new IcTuple[result.size()];
310: for (int index = 0; index < resultArray.length; index++) {
311: resultArray[index] = (IcTuple) result.get(index);
312: }
313: return resultArray;
314: }
315:
316: /**
317: * This performs the actual work of parsing against a non-static instance of
318: * Segment.
319: *
320: * @param in
321: * the input stream to read from
322: * @throws IOException
323: * if a problem occurs during reading from the underlying stream
324: * @throws Pack200Exception
325: * if a problem occurs with an unexpected value or unsupported
326: * codec
327: */
328: private void parseSegment(InputStream in) throws IOException,
329: Pack200Exception {
330: debug("-------");
331: header = new SegmentHeader();
332: header.unpack(in);
333: cpBands = new CpBands(this );
334: cpBands.unpack(in);
335: attrDefinitionBands = new AttrDefinitionBands(this );
336: attrDefinitionBands.unpack(in);
337: icBands = new IcBands(this );
338: icBands.unpack(in);
339: classBands = new ClassBands(this );
340: classBands.unpack(in);
341: bcBands = new BcBands(this );
342: bcBands.unpack(in);
343: fileBands = new FileBands(this );
344: fileBands.unpack(in);
345: }
346:
347: /**
348: * Unpacks a packed stream (either .pack. or .pack.gz) into a corresponding
349: * JarOuputStream.
350: *
351: * @throws Pack200Exception
352: * if there is a problem unpacking
353: * @throws IOException
354: * if there is a problem with I/O during unpacking
355: */
356: public void unpack(InputStream in, JarOutputStream out)
357: throws IOException, Pack200Exception {
358: if (!in.markSupported())
359: in = new BufferedInputStream(in);
360: parseSegment(in);
361: writeJar(out);
362: }
363:
364: /**
365: * This is a local debugging message to aid the developer in writing this
366: * class. It will be removed before going into production. If the property
367: * 'debug.pack200' is set, this will generate messages to stderr; otherwise,
368: * it will be silent.
369: *
370: * @param message
371: * @deprecated this should be removed from production code
372: */
373: protected void debug(String message) {
374: if (System.getProperty("debug.pack200") != null) {
375: System.err.println(message);
376: }
377: }
378:
379: /**
380: * Writes the segment to an output stream. The output stream should be
381: * pre-buffered for efficiency. Also takes the same input stream for
382: * reading, since the file bits may not be loaded and thus just copied from
383: * one stream to another. Doesn't close the output stream when finished, in
384: * case there are more entries (e.g. further segments) to be written.
385: *
386: * @param out
387: * the JarOutputStream to write data to
388: * @param in
389: * the same InputStream that was used to parse the segment
390: * @throws IOException
391: * if an error occurs whilst reading or writing to the streams
392: * @throws Pack200Exception
393: * if an error occurs whilst unpacking data
394: */
395: public void writeJar(JarOutputStream out) throws IOException,
396: Pack200Exception {
397: fileBands.processFileBits();
398: DataOutputStream dos = new DataOutputStream(out);
399: String[] fileName = fileBands.getFileName();
400: long[] fileModtime = fileBands.getFileModtime();
401: long[] fileOptions = fileBands.getFileOptions();
402: long[] fileSize = fileBands.getFileSize();
403: byte[][] fileBits = fileBands.getFileBits();
404:
405: // out.setLevel(JarEntry.DEFLATED)
406: // now write the files out
407: int classNum = 0;
408: int numberOfFiles = header.getNumberOfFiles();
409: long archiveModtime = header.getArchiveModtime();
410: SegmentOptions options = header.getOptions();
411: for (int i = 0; i < numberOfFiles; i++) {
412: String name = fileName[i];
413: long modtime = archiveModtime + fileModtime[i];
414: boolean deflate = (fileOptions[i] & 1) == 1
415: || options.shouldDeflate();
416: if (overrideDeflateHint) { // Overridden by a command line argument
417: deflate = deflateHint;
418: }
419: boolean isClass = (fileOptions[i] & 2) == 2 || name == null
420: || name.equals("");
421: if (isClass) {
422: // pull from headers
423: if (name == null || name.equals(""))
424: name = classBands.getClassThis()[classNum]
425: + ".class";
426: }
427: JarEntry entry = new JarEntry(name);
428: if (deflate)
429: entry.setMethod(JarEntry.DEFLATED);
430: entry.setTime(modtime);
431: out.putNextEntry(entry);
432:
433: if (isClass) {
434: // write to dos
435: ClassFile classFile = buildClassFile(classNum);
436: classFile.write(dos);
437: dos.flush();
438: classNum++;
439: } else {
440: long size = fileSize[i];
441: entry.setSize(size);
442: // TODO pull from in
443: byte[] data = fileBits[i];
444: out.write(data);
445: }
446: }
447: dos.flush();
448: out.finish();
449: out.flush();
450: }
451:
452: public SegmentConstantPool getConstantPool() {
453: return cpBands.getConstantPool();
454: }
455:
456: public SegmentHeader getSegmentHeader() {
457: return header;
458: }
459:
460: protected AttrDefinitionBands getAttrDefinitionBands() {
461: return attrDefinitionBands;
462: }
463:
464: protected BcBands getBcBands() {
465: return bcBands;
466: }
467:
468: protected ClassBands getClassBands() {
469: return classBands;
470: }
471:
472: protected CpBands getCpBands() {
473: return cpBands;
474: }
475:
476: protected FileBands getFileBands() {
477: return fileBands;
478: }
479:
480: protected IcBands getIcBands() {
481: return icBands;
482: }
483:
484: public void setLogLevel(int logLevel) {
485:
486: }
487:
488: public void setLogStream(OutputStream stream) {
489:
490: }
491:
492: public void log(int logLevel, String message) {
493:
494: }
495:
496: /**
497: * Override the archive's deflate hint with the given boolean
498: * @param deflateHint - the deflate hint to use
499: */
500: public void overrideDeflateHint(boolean deflateHint) {
501: this .overrideDeflateHint = true;
502: this.deflateHint = deflateHint;
503: }
504:
505: }
|