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.BufferedReader;
045: import java.io.File;
046: import java.io.FileInputStream;
047: import java.io.FileOutputStream;
048: import java.io.FileReader;
049: import java.io.IOException;
050: import java.util.ArrayList;
051: import java.util.Iterator;
052: import java.util.List;
053: import java.util.regex.Matcher;
054: import java.util.regex.Pattern;
055:
056: import org.apache.tools.ant.BuildException;
057: import org.apache.tools.ant.FileScanner;
058: import org.apache.tools.ant.Project;
059: import org.apache.tools.ant.Task;
060: import org.apache.tools.ant.types.FileSet;
061:
062: /** Check source files for a license notice.
063: * @author Jesse Glick
064: */
065: public class CheckLicense extends Task {
066:
067: private final List<FileSet> filesets = new ArrayList<FileSet>(1);
068: private String fragment;
069: private List<Convert> fragments;
070: private FailType fail;
071:
072: /** Add a file set of source files to check.
073: * @param fs set of files to check licenses of
074: */
075: public void addFileSet(FileSet fs) {
076: filesets.add(fs);
077: }
078:
079: /** Set the fragment of license notice which is expected
080: * to be found in each source file.
081: * @param f the fragment
082: */
083: public void setFragment(String f) {
084: fragment = f;
085: }
086:
087: public void setFail(FailType t) {
088: fail = t;
089: }
090:
091: public Convert createConvert() {
092: Convert f = new Convert();
093: if (fragments == null) {
094: fragments = new ArrayList<Convert>();
095: }
096:
097: fragments.add(f);
098: return f;
099: }
100:
101: public void execute() throws BuildException {
102: if (fragment == null) {
103: if (fragments == null) {
104: throw new BuildException("You must supply a fragment",
105: getLocation());
106: }
107:
108: executeReplace();
109: return;
110: }
111: if (filesets.isEmpty())
112: throw new BuildException(
113: "You must supply at least one fileset",
114: getLocation());
115: Iterator it = filesets.iterator();
116: String failMsg = null;
117: try {
118: while (it.hasNext()) {
119: FileScanner scanner = ((FileSet) it.next())
120: .getDirectoryScanner(getProject());
121: File baseDir = scanner.getBasedir();
122: String[] files = scanner.getIncludedFiles();
123: log("Looking for " + fragment + " in " + files.length
124: + " files in " + baseDir.getAbsolutePath());
125: for (int i = 0; i < files.length; i++) {
126: File f = new File(baseDir, files[i]);
127: //log("Scanning " + f, Project.MSG_VERBOSE);
128: BufferedReader br = new BufferedReader(
129: new FileReader(f));
130: try {
131: String line;
132: while ((line = br.readLine()) != null) {
133: if (line.indexOf(fragment) != -1) {
134: // Found it.
135: if (fail != null
136: && "whenpresent".equals(fail
137: .getValue())) {
138: if (failMsg != null) {
139: log(failMsg, Project.MSG_ERR);
140: }
141: failMsg = "License found in " + f;
142: }
143: break;
144: }
145: }
146: if (line == null) {
147: String msg = f.getAbsolutePath()
148: + ":1: no license notice found";
149: if (fail != null
150: && "whenmissing".equals(fail
151: .getValue())) {
152: throw new BuildException(msg);
153: }
154: if (fail == null) {
155: // Scanned whole file without finding it.
156: log(msg, Project.MSG_ERR);
157: }
158: }
159: } finally {
160: br.close();
161: }
162: }
163: }
164:
165: if (failMsg != null) {
166: throw new BuildException(failMsg);
167: }
168:
169: } catch (IOException ioe) {
170: throw new BuildException(
171: "Could not open files to check licenses", ioe,
172: getLocation());
173: }
174: }
175:
176: private void executeReplace() throws BuildException {
177: Iterator it = filesets.iterator();
178: try {
179: byte[] workingArray = new byte[1024];
180: while (it.hasNext()) {
181: FileScanner scanner = ((FileSet) it.next())
182: .getDirectoryScanner(getProject());
183: File baseDir = scanner.getBasedir();
184: String[] files = scanner.getIncludedFiles();
185: log("Replacing code in " + files.length + " files in "
186: + baseDir.getAbsolutePath());
187: for (int i = 0; i < files.length; i++) {
188: File file = new File(baseDir, files[i]);
189: log("Processing " + file, Project.MSG_VERBOSE);
190: FileInputStream is = new FileInputStream(file);
191: int workingLength = is.read(workingArray);
192: if (workingLength == -1) {
193: continue;
194: }
195: String workingString = new String(workingArray, 0,
196: workingLength);
197: boolean changed = false;
198: String prefix = null;
199:
200: Iterator frags = fragments.iterator();
201: while (frags.hasNext()) {
202: Convert f = (Convert) frags.next();
203:
204: Matcher matcher = f.orig.matcher(workingString);
205:
206: while (matcher.find()) {
207: if (f.prefix) {
208: if (prefix != null) {
209: throw new BuildException(
210: "Only one convert element can be prefix!");
211: }
212: if (matcher.groupCount() != 1) {
213: throw new BuildException(
214: "There should be one group for the prefix element. Was: "
215: + matcher
216: .groupCount());
217: }
218: prefix = matcher.group(1);
219: }
220:
221: String before = workingString.substring(0,
222: matcher.start());
223: String after = workingString
224: .substring(matcher.end());
225: String middle = wrapWithPrefix(f.repl,
226: prefix, before.length() == 0
227: || before.endsWith("\n"));
228:
229: if (!middle.equals(matcher.group(0))) {
230: workingString = before + middle + after;
231: log("Matched " + middle,
232: Project.MSG_VERBOSE);
233: changed = true;
234: } else {
235: log(
236: "Matched, but no change: "
237: + middle,
238: Project.MSG_VERBOSE);
239: }
240:
241: if (!f.all) {
242: break;
243: } else {
244: matcher = f.orig.matcher(workingString);
245: }
246: }
247: }
248:
249: byte[] rest = null;
250: if (is.available() > 0 && changed) {
251: rest = new byte[is.available()];
252: int read = is.read(rest);
253: assert read == rest.length;
254: }
255:
256: is.close();
257:
258: if (changed) {
259: log("Rewriting " + file);
260: FileOutputStream os = new FileOutputStream(file);
261: workingString = Pattern.compile(" +$",
262: Pattern.MULTILINE).matcher(
263: workingString + "X").replaceAll("");
264: os.write(workingString.substring(0,
265: workingString.length() - 1).getBytes());
266: if (rest != null) {
267: os.write(rest);
268: }
269: os.close();
270: }
271: }
272: }
273: } catch (IOException ioe) {
274: throw new BuildException(
275: "Could not open files to check licenses", ioe,
276: getLocation());
277: }
278: }
279:
280: private String wrapWithPrefix(String repl, String prefix,
281: boolean startWithPrefix) {
282: if (prefix == null) {
283: return repl;
284: }
285:
286: String[] all = repl.split("\n");
287: StringBuffer sb = new StringBuffer();
288: for (int i = 0; i < all.length; i++) {
289: if (startWithPrefix) {
290: sb.append(prefix);
291: }
292: sb.append(all[i]);
293: if (i < all.length - 1) {
294: sb.append('\n');
295: }
296: startWithPrefix = true;
297: }
298:
299: return sb.toString();
300: }
301:
302: public static final class Convert {
303: Pattern orig;
304: String repl;
305: boolean prefix;
306: boolean all;
307:
308: public void setToken(String orig) {
309: this .orig = Pattern.compile(orig, Pattern.DOTALL
310: | Pattern.MULTILINE);
311: }
312:
313: public void setReplace(String repl) {
314: this .repl = repl.replace("\\n", "\n").replace("\\t", "\t");
315: }
316:
317: public void setPrefix(boolean b) {
318: prefix = b;
319: }
320:
321: public void setReplaceAll(boolean b) {
322: all = b;
323: }
324:
325: public Line createLine() {
326: return new Line();
327: }
328:
329: public final class Line {
330: public void setText(String t) {
331: if (repl == null) {
332: repl = t;
333: } else {
334: repl = repl + "\n" + t;
335: }
336: }
337: }
338: }
339:
340: public static final class FailType extends
341: org.apache.tools.ant.types.EnumeratedAttribute {
342: public String[] getValues() {
343: return new String[] { "whenmissing", "whenpresent", };
344: }
345: }
346: }
|