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.BufferedInputStream;
045: import java.io.BufferedReader;
046: import java.io.ByteArrayInputStream;
047: import java.io.File;
048: import java.io.FileInputStream;
049: import java.io.FileReader;
050: import java.io.FilenameFilter;
051: import java.io.IOException;
052: import java.io.InputStream;
053: import java.util.ArrayList;
054: import java.util.Collection;
055: import java.util.HashMap;
056: import java.util.HashSet;
057: import java.util.LinkedHashMap;
058: import java.util.Map;
059: import java.util.TreeMap;
060: import java.util.jar.Attributes;
061: import java.util.jar.Manifest;
062: import javax.xml.parsers.SAXParser;
063: import javax.xml.parsers.SAXParserFactory;
064: import org.apache.tools.ant.BuildException;
065: import org.apache.tools.ant.Project;
066: import org.apache.tools.ant.Task;
067: import org.xml.sax.EntityResolver;
068: import org.xml.sax.InputSource;
069: import org.xml.sax.SAXException;
070: import org.xml.sax.XMLReader;
071: import org.xml.sax.helpers.DefaultHandler;
072:
073: /** Task that scans Bundle.properties files for unused keys.
074: *
075: * @author Radim Kubacki
076: */
077: public class CheckBundles extends Task {
078:
079: private static HashSet knownKeys;
080:
081: private static String[] moduleKeys = new String[] {
082: "OpenIDE-Module-Name", "OpenIDE-Module-Display-Category",
083: "OpenIDE-Module-Long-Description",
084: "OpenIDE-Module-Short-Description",
085: "OpenIDE-Module-Package-Dependency-Message" };
086:
087: private File srcdir;
088:
089: public void setSrcdir(File f) {
090: // Note: f will automatically be absolute (resolved from project basedir).
091: if (!f.isDirectory())
092: throw new IllegalArgumentException(f
093: + " must be a directory");
094:
095: srcdir = f;
096: }
097:
098: public void execute() throws BuildException {
099: log("Scanning " + srcdir.getAbsolutePath(), Project.MSG_VERBOSE);
100:
101: Map<String, File> knownNames = parseManifest(srcdir);
102:
103: Collection<File> bundles = new ArrayList<File>();
104: Map<File, String[]> sources = new TreeMap<File, String[]>();
105:
106: try {
107: File dir = new File(srcdir, "src");
108: if (dir.exists())
109: scanSubdirs(dir, bundles, sources);
110: dir = new File(srcdir, "libsrc");
111: if (dir.exists())
112: scanSubdirs(dir, bundles, sources);
113: // XXX there are still a lot of unsplit bundles in e.g. core/src/
114: // referred to from other submodules
115: // XXX this technique does not work so well, though - should only scan
116: // them for source files, not bundles...
117: /*
118: File[] subdirs = srcdir.listFiles();
119: if (subdirs != null) {
120: for (int i = 0; i < subdirs.length; i++) {
121: if (subdirs[i].isDirectory()) {
122: dir = new File(subdirs[i], "src");
123: if (dir.exists()) {
124: scanSubdirs(dir, bundles, sources);
125: }
126: }
127: }
128: }
129: */
130: check(bundles, sources, knownNames);
131: } catch (Exception e) {
132: throw new BuildException(e);
133: }
134: }
135:
136: private void scan(File file, Collection<File> bundles,
137: Map<File, String[]> sources) throws Exception {
138: File bundle = new File(file, "Bundle.properties");
139: if (!bundle.exists()) {
140: log("No bundle in " + file.getAbsolutePath() + ". OK",
141: Project.MSG_VERBOSE);
142: } else {
143: bundles.add(bundle);
144: }
145:
146: addSources(file, sources);
147: }
148:
149: private void check(Collection<File> bundles,
150: Map<File, String[]> files, Map<String, File> knownNames) {
151: try {
152: for (File bundle : bundles) {
153: for (Map.Entry<String, Integer> entry : entries(bundle)
154: .entrySet()) {
155: String key = entry.getKey();
156: int line = entry.getValue();
157: log("Looking for " + key, Project.MSG_DEBUG);
158: boolean found = false;
159: // module info or file name from layer
160: if (bundle.equals(knownNames.get(key))) {
161: log("Checked name " + key + " OK",
162: Project.MSG_VERBOSE);
163: found = true;
164: } else {
165: // java source in the same package
166: for (String src : files.get(bundle
167: .getParentFile())) {
168: if (src.indexOf("\"" + key + "\"") >= 0) {
169: log("Checking " + key + " OK",
170: Project.MSG_VERBOSE);
171: found = true;
172: break;
173: }
174: }
175: }
176: if (!found) {
177: // try other java sources
178: // XXX should skip moduleKeys, etc.
179: for (Map.Entry<File, String[]> entry2 : files
180: .entrySet()) {
181: File dir = entry2.getKey();
182: for (String src : entry2.getValue()) {
183: if (src.indexOf("\"" + key + "\"") >= 0) {
184: log(bundle.getPath() + ":" + line
185: + ": " + key
186: + " used from "
187: + dir.getPath(),
188: Project.MSG_WARN);
189: found = true;
190: break;
191: }
192: }
193: }
194: if (!found) {
195: log(bundle.getPath() + ":" + line + ": "
196: + key + " NOT FOUND");
197: }
198: }
199: }
200:
201: }
202: } catch (Exception e) {
203: e.printStackTrace();
204: }
205: }
206:
207: private void scanSubdirs(File file, Collection<File> bundles,
208: Map<File, String[]> srcs) throws Exception {
209: log("scanSubdirs " + file, Project.MSG_DEBUG);
210: File[] subdirs = file.listFiles(new FilenameFilter() {
211: public boolean accept(File f, String name) {
212: return new File(f, name).isDirectory();
213: }
214: });
215: for (int i = 0; i < subdirs.length; i++) {
216: scan(subdirs[i], bundles, srcs);
217: scanSubdirs(subdirs[i], bundles, srcs);
218: }
219:
220: }
221:
222: /** Adds dir -> array of source texts */
223: private void addSources(File dir, Map<File, String[]> map)
224: throws Exception {
225: File[] files = dir.listFiles(new FilenameFilter() {
226: public boolean accept(File dir, String name) {
227: if (name.endsWith(".java")) {
228: return true;
229: }
230: return false;
231: }
232: });
233: String[] srcs = new String[files.length];
234: for (int i = 0; i < files.length; i++) {
235: InputStream is = new BufferedInputStream(
236: new FileInputStream(files[i]));
237: byte[] arr = new byte[2048];
238: srcs[i] = "";
239: int len;
240: while ((len = is.read(arr)) != -1) {
241: srcs[i] = srcs[i] + new String(arr, 0, len);
242: }
243: }
244: map.put(dir, srcs);
245: return;
246: }
247:
248: /**
249: * Get a list of keys in a bundle file, with accompanying line numbers.
250: */
251: private Map<String, Integer> entries(File bundle)
252: throws IOException {
253: Map<String, Integer> entries = new LinkedHashMap<String, Integer>();
254: BufferedReader r = new BufferedReader(new FileReader(bundle));
255: String l;
256: boolean multi = false;
257: int line = 0;
258: while ((l = r.readLine()) != null) {
259: line++;
260: if (!l.startsWith("#")) {
261:
262: int i = l.indexOf('=');
263: if (i > 0 && !multi) {
264: String key = l.substring(0, i).trim();
265: entries.put(key, line);
266: }
267: if (l.endsWith("\\"))
268: multi = true;
269: else
270: multi = false;
271: }
272: }
273: return entries;
274: }
275:
276: private Map<String, File> parseManifest(File dir) {
277: Map<String, File> files = new HashMap<String, File>(10);
278: try {
279: File mf = new File(srcdir, "manifest.mf");
280: if (!mf.exists()) {
281: log("Manifest file not found", Project.MSG_VERBOSE);
282: return files;
283: }
284:
285: log("Found manifest", Project.MSG_VERBOSE);
286:
287: Manifest m = new Manifest(new FileInputStream(mf));
288: Attributes attr = m.getMainAttributes();
289:
290: // Try to find bundle
291: String lb = (attr == null) ? null : attr
292: .getValue("OpenIDE-Module-Localizing-Bundle");
293: if (lb != null) {
294: File lbundle = new File(srcdir.getAbsolutePath()
295: + File.separator + "src" + File.separatorChar
296: + lb);
297: log("Recognized localizing bundle " + lbundle,
298: Project.MSG_VERBOSE);
299: for (int i = 0; i < moduleKeys.length; i++) {
300: files.put(moduleKeys[i], lbundle);
301: }
302: }
303:
304: // Try to find XML layer
305: String xml = (attr == null) ? null : attr
306: .getValue("OpenIDE-Module-Layer");
307: File xmlFile = null;
308: if (xml != null) {
309: xmlFile = new File(srcdir.getAbsolutePath()
310: + File.separator + "src" + File.separator + xml);
311: }
312: if (xmlFile != null && xmlFile.exists()) {
313: SAXParserFactory f = SAXParserFactory.newInstance();
314: f.setValidating(false);
315: SAXParser p = f.newSAXParser();
316: XMLReader reader = p.getXMLReader();
317: reader.setEntityResolver(new EntityResolver() {
318: public InputSource resolveEntity(String publicId,
319: String systemId) {
320: log("resolveEntity " + publicId + ", "
321: + systemId, Project.MSG_DEBUG);
322: // if ("-//NetBeans//DTD Filesystem 1.0//EN".equals (publicId)
323: // || "-//NetBeans//DTD Filesystem 1.1//EN".equals (publicId))
324: return new InputSource(
325: new ByteArrayInputStream(new byte[0]));
326: }
327: });
328: reader.setContentHandler(new SAXHandler(files));
329: reader
330: .parse(new InputSource(xmlFile.toURI()
331: .toString()));
332: }
333: } catch (Exception e) {
334: throw new BuildException(e);
335: }
336:
337: return files;
338: }
339:
340: private class SAXHandler extends DefaultHandler {
341:
342: private String path;
343:
344: private Map<String, File> map;
345:
346: public SAXHandler(Map<String, File> map) {
347: this .map = map;
348: }
349:
350: public void startDocument() throws SAXException {
351: super .startDocument();
352: path = "";
353: }
354:
355: public void endElement(String uri, String lname, String name)
356: throws SAXException {
357: super .endElement(uri, lname, name);
358: if ("folder".equals(name) || "file".equals(name)) {
359: int i = path.lastIndexOf('/');
360: path = (i > 0) ? path.substring(0, i) : "";
361: }
362: }
363:
364: public void startElement(String uri, String lname, String name,
365: org.xml.sax.Attributes attributes) throws SAXException {
366: super .startElement(uri, lname, name, attributes);
367: // log("Handling "+uri+", "+lname+", "+name+", "+attributes, Project.MSG_DEBUG);
368: if ("folder".equals(name) || "file".equals(name)) {
369: String f = attributes.getValue("name");
370: if (name != null) {
371: path += (path.length() == 0) ? f : "/" + f;
372: }
373: } else if ("attr".equals(name)) {
374: String a = attributes.getValue("name");
375: if ("SystemFileSystem.localizingBundle".equals(a)) {
376: String val = attributes.getValue("stringvalue");
377: String lfilename = srcdir.getAbsolutePath()
378: + File.separator + "src" + File.separator
379: + val.replace('.', File.separatorChar)
380: + ".properties";
381: File lfile = new File(lfilename);
382: log("Recognized file " + path
383: + " with name localized in " + lfile,
384: Project.MSG_VERBOSE);
385: for (int i = 0; i < moduleKeys.length; i++) {
386: map.put(path, lfile);
387: }
388: }
389: }
390: }
391:
392: }
393: }
|