1 /*******************************************************************************
2  * Copyright (c) 2000, 2013 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *******************************************************************************/
14 package org.eclipse.jdt.internal.compiler.util;
15 
16 import java.io.IOException;
17 import java.io.InputStream;
18 import java.util.ArrayList;
19 import java.util.List;
20 
21 @SuppressWarnings({"rawtypes", "unchecked"})
22 public class ManifestAnalyzer {
23 	private static final int
24 		START = 0,
25 		IN_CLASSPATH_HEADER = 1, // multistate
26 		PAST_CLASSPATH_HEADER = 2,
27 		SKIPPING_WHITESPACE = 3,
28 		READING_JAR = 4,
29 		CONTINUING = 5,
30 		SKIP_LINE = 6;
31 	private static final char[] CLASSPATH_HEADER_TOKEN =
32 		"Class-Path:".toCharArray(); //$NON-NLS-1$
33 	private int classpathSectionsCount;
34 	private ArrayList calledFilesNames;
35 
36 	/**
37 	 * Analyzes the manifest contents. The given input stream is read using a UTF-8 encoded reader.
38 	 * If the contents of the input stream is not encoded using a UTF-8 encoding, the analysis will fail.
39 	 *
40 	 * @param inputStream the given input stream.
41 	 *
42 	 * @return <code>true</code> if the analysis is successful, <code>false</code> otherwise.
43 	 * @throws IOException if an exception occurs while analyzing the file
44 	 */
analyzeManifestContents(InputStream inputStream)45 	public boolean analyzeManifestContents(InputStream inputStream) throws IOException {
46 		char[] chars = Util.getInputStreamAsCharArray(inputStream, -1, Util.UTF_8);
47 		return analyzeManifestContents(chars);
48 	}
49 
50 	/**
51 	 * Analyzes the manifest contents.
52 	 *
53 	 * @param chars the content of the manifest
54 	 *
55 	 * @return <code>true</code> if the analysis is successful, <code>false</code> otherwise.
56 	 */
analyzeManifestContents(char[] chars)57 	public boolean analyzeManifestContents(char[] chars) {
58 		int state = START, substate = 0;
59 		StringBuffer currentJarToken = new StringBuffer();
60 		int currentChar;
61 		this.classpathSectionsCount = 0;
62 		this.calledFilesNames = null;
63 		for (int i = 0, max = chars.length; i < max;) {
64 			currentChar = chars[i++];
65 			if (currentChar == '\r') {
66 				// skip \r, will consider \n later (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=251079 )
67 				if (i < max) {
68 					currentChar = chars[i++];
69 				}
70 			}
71 			switch (state) {
72 				case START:
73 					if (currentChar == CLASSPATH_HEADER_TOKEN[0]) {
74 						state = IN_CLASSPATH_HEADER;
75 						substate = 1;
76 					} else {
77 						state = SKIP_LINE;
78 					}
79 					break;
80 				case IN_CLASSPATH_HEADER:
81 					if (currentChar == '\n') {
82 						state = START;
83 					} else if (currentChar != CLASSPATH_HEADER_TOKEN[substate++]) {
84 						state = SKIP_LINE;
85 					} else if (substate == CLASSPATH_HEADER_TOKEN.length) {
86 						state = PAST_CLASSPATH_HEADER;
87 					}
88 					break;
89 				case PAST_CLASSPATH_HEADER:
90 					if (currentChar == ' ') {
91 						state = SKIPPING_WHITESPACE;
92 						this.classpathSectionsCount++;
93 					} else {
94 						return false;
95 					}
96 					break;
97 				case SKIPPING_WHITESPACE:
98 					if (currentChar == '\n') {
99 						state = CONTINUING;
100 					} else if (currentChar != ' ') {
101 						currentJarToken.append((char) currentChar);
102 						state = READING_JAR;
103 					} else {
104 						// >>>>>>>>>>>>>>>>>> Add the latest jar read
105 						addCurrentTokenJarWhenNecessary(currentJarToken);
106 					}
107 					break;
108 				case CONTINUING:
109 					if (currentChar == '\n') {
110 						addCurrentTokenJarWhenNecessary(currentJarToken);
111 						state = START;
112 					} else if (currentChar == ' ') {
113 						state = SKIPPING_WHITESPACE;
114 					} else if (currentChar == CLASSPATH_HEADER_TOKEN[0]) {
115 						addCurrentTokenJarWhenNecessary(currentJarToken);
116 						state = IN_CLASSPATH_HEADER;
117 						substate = 1;
118 					} else if (this.calledFilesNames == null) {
119 						// >>>>>>>>>>>>>>>>>> Add the latest jar read
120 						addCurrentTokenJarWhenNecessary(currentJarToken);
121 						state = START;
122 					} else {
123 						// >>>>>>>>>>>>>>>>>> Add the latest jar read
124 						addCurrentTokenJarWhenNecessary(currentJarToken);
125 						state = SKIP_LINE;
126 					}
127 					break;
128 				case SKIP_LINE:
129 					if (currentChar == '\n') {
130 						state = START;
131 					}
132 					break;
133 				case READING_JAR:
134 					if (currentChar == '\n') {
135 						// appends token below
136 						state = CONTINUING;
137 						// >>>>>>>>>>> Add a break to not add the jar yet as it can continue on the next line
138 						break;
139 					} else if (currentChar == ' ') {
140 						// appends token below
141 						state = SKIPPING_WHITESPACE;
142 					} else {
143 						currentJarToken.append((char) currentChar);
144 						break;
145 					}
146 					addCurrentTokenJarWhenNecessary(currentJarToken);
147 					break;
148 			}
149 		}
150 		switch (state) {
151 			case START:
152 				return true;
153 			case IN_CLASSPATH_HEADER:
154 				return true;
155 			case PAST_CLASSPATH_HEADER:
156 				return false;
157 			case SKIPPING_WHITESPACE:
158 				// >>>>>>>>>>>>>>>>>> Add the latest jar read
159 				addCurrentTokenJarWhenNecessary(currentJarToken);
160 				return true;
161 			case CONTINUING:
162 				// >>>>>>>>>>>>>>>>>> Add the latest jar read
163 				addCurrentTokenJarWhenNecessary(currentJarToken);
164 				return true;
165 			case SKIP_LINE:
166 				if (this.classpathSectionsCount != 0) {
167 					if (this.calledFilesNames == null) {
168 						return false;
169 					}
170 				}
171 				return true;
172 			case READING_JAR:
173 				// >>>>>>>>>>>>>>>>>> Add the latest jar read
174 				return false;
175 		}
176 		return true;
177 	}
178 
179 	// >>>>>>>>>>>>>>>> Method Extracted from analyzeManifestContents in the READING_JAR Block
addCurrentTokenJarWhenNecessary(StringBuffer currentJarToken)180 	private boolean addCurrentTokenJarWhenNecessary(StringBuffer currentJarToken) {
181 		if (currentJarToken != null && currentJarToken.length() > 0) {
182 			if (this.calledFilesNames == null) {
183 				this.calledFilesNames = new ArrayList();
184 			}
185 			this.calledFilesNames.add(currentJarToken.toString());
186 			currentJarToken.setLength(0);
187 			return true;
188 		}
189 		return false;
190 	}
191 	// <<<<<<<<<<<<<<<<<<<<<<
192 
193 
getClasspathSectionsCount()194 	public int getClasspathSectionsCount() {
195 		return this.classpathSectionsCount;
196 	}
getCalledFileNames()197 	public List getCalledFileNames() {
198 		return this.calledFilesNames;
199 	}
200 }
201