001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.nbbuild;
043:
044: import java.io.File;
045: import java.io.FileNotFoundException;
046: import java.io.IOException;
047: import java.io.InputStream;
048: import java.io.PrintWriter;
049: import java.io.StringReader;
050: import java.util.ArrayList;
051: import java.util.Comparator;
052: import java.util.List;
053: import java.util.Map;
054: import java.util.Properties;
055: import java.util.SortedMap;
056: import java.util.SortedSet;
057: import java.util.StringTokenizer;
058: import java.util.TreeMap;
059: import java.util.TreeSet;
060: import java.util.jar.JarFile;
061: import java.util.jar.Manifest;
062: import java.util.zip.ZipEntry;
063: import javax.xml.parsers.SAXParserFactory;
064: import org.apache.tools.ant.BuildException;
065: import org.apache.tools.ant.DirectoryScanner;
066: import org.apache.tools.ant.Project;
067: import org.apache.tools.ant.Task;
068: import org.apache.tools.ant.types.FileSet;
069: import org.xml.sax.Attributes;
070: import org.xml.sax.InputSource;
071: import org.xml.sax.SAXException;
072: import org.xml.sax.helpers.DefaultHandler;
073:
074: /**
075: * Task to scan all XML layers in a NB installation
076: * and report on which modules registers which files.
077: * @author Jesse Glick
078: */
079: public class LayerIndex extends Task {
080:
081: public LayerIndex() {
082: }
083:
084: List<FileSet> filesets = new ArrayList<FileSet>();
085:
086: public void addConfiguredModules(FileSet fs) {
087: filesets.add(fs);
088: }
089:
090: private File output;
091:
092: public void setOutput(File f) {
093: output = f;
094: }
095:
096: @Override
097: public void execute() throws BuildException {
098: if (filesets.isEmpty()) {
099: throw new BuildException();
100: }
101: SortedMap<String, String> files = new TreeMap<String, String>(); // layer path -> cnb
102: SortedMap<String, SortedMap<String, String>> labels = new TreeMap<String, SortedMap<String, String>>(); // layer path -> cnb -> label
103: final Map<String, Integer> positions = new TreeMap<String, Integer>(); // layer path -> position
104: for (FileSet fs : filesets) {
105: DirectoryScanner ds = fs.getDirectoryScanner(getProject());
106: File basedir = ds.getBasedir();
107: for (String path : ds.getIncludedFiles()) {
108: File jar = new File(basedir, path);
109: try {
110: JarFile jf = new JarFile(jar);
111: try {
112: Manifest mf = jf.getManifest();
113: if (mf == null) {
114: continue;
115: }
116: String modname = mf.getMainAttributes()
117: .getValue("OpenIDE-Module");
118: if (modname == null) {
119: continue;
120: }
121: String cnb = modname.replaceFirst("/\\d+$", "");
122: String layer = mf.getMainAttributes().getValue(
123: "OpenIDE-Module-Layer");
124: if (layer == null) {
125: continue;
126: }
127: parse(jf.getInputStream(jf.getEntry(layer)),
128: files, labels, positions, cnb, jf);
129: } finally {
130: jf.close();
131: }
132: } catch (Exception x) {
133: throw new BuildException("Reading " + jar + ": "
134: + x, x, getLocation());
135: }
136: }
137: }
138: int maxlength = 0;
139: for (String cnb : files.values()) {
140: maxlength = Math.max(maxlength, shortenCNB(cnb).length());
141: }
142: try {
143: PrintWriter pw = output != null ? new PrintWriter(output)
144: : null;
145: SortedSet<String> layerPaths = new TreeSet<String>(
146: new Comparator<String>() {
147: public int compare(String p1, String p2) {
148: StringTokenizer tok1 = new StringTokenizer(
149: p1, "/");
150: StringTokenizer tok2 = new StringTokenizer(
151: p2, "/");
152: String prefix = "";
153: while (tok1.hasMoreTokens()) {
154: String piece1 = tok1.nextToken();
155: if (tok2.hasMoreTokens()) {
156: String piece2 = tok2.nextToken();
157: if (piece1.equals(piece2)) {
158: prefix += piece1 + "/";
159: } else {
160: Integer pos1 = pos(prefix
161: + piece1);
162: Integer pos2 = pos(prefix
163: + piece2);
164: if (pos1 == null) {
165: if (pos2 == null) {
166: return piece1
167: .compareTo(piece2);
168: } else {
169: return 1;
170: }
171: } else {
172: if (pos2 == null) {
173: return -1;
174: } else {
175: int diff = pos1 - pos2;
176: if (diff != 0) {
177: return diff;
178: } else {
179: return piece1
180: .compareTo(piece2);
181: }
182: }
183: }
184: }
185: } else {
186: return 1;
187: }
188: }
189: if (tok2.hasMoreTokens()) {
190: return -1;
191: }
192: assert p1.equals(p2) : p1 + " vs. " + p2;
193: return 0;
194: }
195:
196: Integer pos(String path) {
197: return positions.containsKey(path) ? positions
198: .get(path)
199: : positions.get(path + "/");
200: }
201: });
202: layerPaths.addAll(files.keySet());
203: SortedSet<String> remaining = new TreeSet<String>(files
204: .keySet());
205: remaining.removeAll(layerPaths);
206: assert remaining.isEmpty() : remaining;
207: for (String path : layerPaths) {
208: String cnb = files.get(path);
209: String line = String.format("%-" + maxlength + "s %s",
210: shortenCNB(cnb), shortenPath(path));
211: Integer pos = positions.get(path);
212: if (pos != null) {
213: line += String.format(" @%d", pos);
214: }
215: SortedMap<String, String> cnb2Label = labels.get(path);
216: if (cnb2Label != null) {
217: if (cnb2Label.size() == 1
218: && cnb2Label.keySet().iterator().next()
219: .equals(cnb)) {
220: line += String.format(" (\"%s\")", cnb2Label
221: .values().iterator().next());
222: } else {
223: for (Map.Entry<String, String> labelEntry : cnb2Label
224: .entrySet()) {
225: line += String.format(" (%s: \"%s\")",
226: shortenCNB(labelEntry.getKey()),
227: labelEntry.getValue());
228: }
229: }
230: }
231: if (pw != null) {
232: pw.println(line);
233: } else {
234: log(line);
235: }
236: }
237: if (pw != null) {
238: pw.close();
239: }
240: } catch (FileNotFoundException x) {
241: throw new BuildException(x, getLocation());
242: }
243: if (output != null) {
244: log(output + ": layer index written");
245: }
246: }
247:
248: private String shortenCNB(String cnb) {
249: if (cnb != null) {
250: return cnb.replaceFirst("^org\\.netbeans\\.", "o.n.")
251: .replaceFirst("^org\\.openide\\.", "o.o.")
252: .replaceFirst("\\.modules\\.", ".m.");
253: } else {
254: return "";
255: }
256: }
257:
258: private String shortenPath(String path) {
259: return path.replaceAll("(^|/)org-netbeans-", "$1o-n-")
260: .replaceAll("(^|/)org-openide-", "$1o-o-").replaceAll(
261: "-modules-", "-m-").replaceAll(
262: "(^|/)org\\.netbeans\\.", "$1o.n.").replaceAll(
263: "(^|/)org\\.openide\\.", "$1o.o.").replaceAll(
264: "\\.modules\\.", ".m.");
265: }
266:
267: private void parse(InputStream is, final Map<String, String> files,
268: final SortedMap<String, SortedMap<String, String>> labels,
269: final Map<String, Integer> positions, final String cnb,
270: final JarFile jf) throws Exception {
271: SAXParserFactory f = SAXParserFactory.newInstance();
272: f.setValidating(false);
273: f.setNamespaceAware(false);
274: f.newSAXParser().parse(is, new DefaultHandler() {
275: String prefix = "";
276:
277: void register(String path) {
278: if (files.containsKey(path)) {
279: files.put(path, null); // >1 owner
280: } else {
281: files.put(path, cnb);
282: }
283: }
284:
285: @Override
286: public void startElement(String uri, String localName,
287: String qName, Attributes attributes)
288: throws SAXException {
289: if (qName.equals("folder")) {
290: String n = attributes.getValue("name");
291: prefix += n + "/";
292: register(prefix);
293: } else if (qName.equals("file")) {
294: String n = attributes.getValue("name");
295: prefix += n;
296: register(prefix);
297: } else if (qName.equals("attr")
298: && attributes.getValue("name").equals(
299: "SystemFileSystem.localizingBundle")) {
300: String bundlepath = attributes.getValue(
301: "stringvalue").replace('.', '/')
302: + ".properties";
303: Properties props = new Properties();
304: try {
305: ZipEntry entry = jf.getEntry(bundlepath);
306: if (entry == null) {
307: log(bundlepath
308: + " not found in reference from "
309: + prefix + " in " + cnb,
310: Project.MSG_WARN);
311: return;
312: }
313: props.load(jf.getInputStream(entry));
314: } catch (IOException x) {
315: throw new SAXException(x);
316: }
317: String key = prefix.replaceAll("/$", "");
318: String label = props.getProperty(key);
319: if (label == null) {
320: log("Key " + key + " not found in "
321: + bundlepath + " from " + cnb,
322: Project.MSG_WARN);
323: return;
324: }
325: SortedMap<String, String> cnb2label = labels
326: .get(prefix);
327: if (cnb2label == null) {
328: cnb2label = new TreeMap<String, String>();
329: labels.put(prefix, cnb2label);
330: }
331: cnb2label.put(cnb, label);
332: } else if (qName.equals("attr")
333: && attributes.getValue("name").equals(
334: "position")) {
335: String intvalue = attributes.getValue("intvalue");
336: if (intvalue != null
337: && /* #107550 */!intvalue.equals("0")) {
338: try {
339: positions.put(prefix, Integer
340: .parseInt(intvalue));
341: } catch (NumberFormatException x) {
342: throw new SAXException(x);
343: }
344: }
345: }
346: }
347:
348: @Override
349: public void endElement(String uri, String localName,
350: String qName) throws SAXException {
351: if (qName.equals("folder")) {
352: prefix = prefix.replaceFirst("[^/]+/$", "");
353: } else if (qName.equals("file")) {
354: prefix = prefix.replaceFirst("[^/]+$", "");
355: }
356: }
357:
358: @Override
359: public InputSource resolveEntity(String pub, String sys)
360: throws IOException, SAXException {
361: return new InputSource(new StringReader(""));
362: }
363: });
364: }
365:
366: }
|