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.apache.tools.ant.module.api.support;
043:
044: import java.io.IOException;
045: import java.util.ArrayList;
046: import java.util.Collection;
047: import java.util.LinkedHashSet;
048: import java.util.List;
049: import java.util.Properties;
050: import java.util.regex.Matcher;
051: import java.util.regex.Pattern;
052: import org.apache.tools.ant.module.api.AntProjectCookie;
053: import org.apache.tools.ant.module.api.AntTargetExecutor;
054: import org.openide.execution.ExecutorTask;
055: import org.openide.filesystems.FileObject;
056: import org.openide.filesystems.FileUtil;
057: import org.openide.loaders.DataObject;
058: import org.openide.util.Lookup;
059:
060: /**
061: * Makes it easier to implement <code>org.netbeans.spi.project.ActionProvider</code> in a standard way
062: * by running targets in Ant scripts.
063: * @see <a href="@PROJECTS/PROJECTAPI@/org/netbeans/spi/project/ActionProvider.html"><code>ActionProvider</code></a>
064: * @author Jesse Glick
065: */
066: public final class ActionUtils {
067:
068: private ActionUtils() {
069: }
070:
071: /**
072: * Runs an Ant target (or a sequence of them).
073: * @param buildXml an Ant build script
074: * @param targetNames one or more targets to run; or null for the default target
075: * @param properties any Ant properties to define, or null
076: * @return a task tracking the progress of Ant
077: * @throws IOException if there was a problem starting Ant
078: * @throws IllegalArgumentException if you did not provide any targets
079: */
080: public static ExecutorTask runTarget(FileObject buildXml,
081: String[] targetNames, Properties properties)
082: throws IOException, IllegalArgumentException {
083: if (buildXml == null) {
084: throw new NullPointerException(
085: "Must pass non-null build script"); // NOI18N
086: }
087: if (targetNames != null && targetNames.length == 0) {
088: throw new IllegalArgumentException("No targets supplied"); // NOI18N
089: }
090: AntProjectCookie apc = TargetLister
091: .getAntProjectCookie(buildXml);
092: AntTargetExecutor.Env execenv = new AntTargetExecutor.Env();
093: if (properties != null) {
094: Properties p = execenv.getProperties();
095: p.putAll(properties);
096: execenv.setProperties(p);
097: }
098: return AntTargetExecutor.createTargetExecutor(execenv).execute(
099: apc, targetNames);
100: }
101:
102: /**
103: * Convenience method to find a file selection in a selection (context).
104: * All files must exist on disk (according to {@link FileUtil#toFile}).
105: * If a constraining directory is supplied, they must also be contained in it.
106: * If a constraining file suffix is supplied, the base names of the files
107: * must end with that suffix.
108: * The return value is null if there are no matching files; or if the strict
109: * parameter is true and some of the files in the selection did not match
110: * the constraints (disk files, directory, and/or suffix).
111: * <p class="nonnormative">
112: * Typically {@link org.openide.loaders.DataNode}s will form a node selection
113: * which will be placed in the context. This method does <em>not</em> directly
114: * look for nodes in the selection; but generally the lookups of the nodes in
115: * a node selection are spliced into the context as well, so the {@link DataObject}s
116: * should be available. A corollary of not checking nodes directly is that any
117: * nodes in the context which do not correspond to files at all (i.e. do not have
118: * {@link DataObject} as a cookie) are ignored, even with the strict parameter on;
119: * and that multiple nodes in the context with the same associated file are treated
120: * as a single entry.
121: * </p>
122: * @param context a selection as provided to e.g. <code>ActionProvider.isActionEnabled(...)</code>
123: * @param dir a constraining parent directory, or null to not check for a parent directory
124: * @param suffix a file suffix (e.g. <samp>.java</samp>) to constrain files by,
125: * or null to not check suffixes
126: * @param strict if true, all files in the selection have to be accepted
127: * @return a nonempty selection of disk files, or null
128: * @see <a href="@PROJECTS/PROJECTAPI@/org/netbeans/spi/project/ActionProvider.html#isActionEnabled(java.lang.String,%20org.openide.util.Lookup)"><code>ActionProvider.isActionEnabled(...)</code></a>
129: */
130: public static FileObject[] findSelectedFiles(Lookup context,
131: FileObject dir, String suffix, boolean strict) {
132: if (dir != null && !dir.isFolder()) {
133: throw new IllegalArgumentException("Not a folder: " + dir); // NOI18N
134: }
135: if (suffix != null && suffix.indexOf('/') != -1) {
136: throw new IllegalArgumentException(
137: "Cannot includes slashes in suffix: " + suffix); // NOI18N
138: }
139: Collection<FileObject> files = new LinkedHashSet<FileObject>(); // #50644: remove dupes
140: // XXX this should perhaps also check for FileObject's...
141: for (DataObject d : context.lookupAll(DataObject.class)) {
142: FileObject f = d.getPrimaryFile();
143: boolean matches = FileUtil.toFile(f) != null;
144: if (dir != null) {
145: matches &= (FileUtil.isParentOf(dir, f) || dir == f);
146: }
147: if (suffix != null) {
148: matches &= f.getNameExt().endsWith(suffix);
149: }
150: // Generally only files from one project will make sense.
151: // Currently the action UI infrastructure (PlaceHolderAction)
152: // checks for that itself. Should there be another check here?
153: if (matches) {
154: files.add(f);
155: } else if (strict) {
156: return null;
157: }
158: }
159: if (files.isEmpty()) {
160: return null;
161: }
162: return files.toArray(new FileObject[files.size()]);
163: }
164:
165: /**
166: * Map files of one kind in a source directory to files of another kind in a target directory.
167: * You may use regular expressions to remap file names in the process.
168: * Only files which actually exist in the target directory will be returned.
169: * <span class="nonnormative">(If you expect the target files to be created
170: * by Ant you do not need this method, since Ant's mappers suffice.)</span>
171: * The file paths considered by the regular expression (if supplied) always use
172: * <samp>/</samp> as the separator.
173: * <p class="nonnormative">
174: * Typical usage to map a set of Java source files to corresponding tests:
175: * <code>regexpMapFiles(files, srcDir, Pattern.compile("/([^/]+)\\.java"), testSrcDir, "/\\1Test.java", true)</code>
176: * </p>
177: * @param fromFiles a list of source files to start with (may be empty)
178: * @param fromDir a directory in which all the source files reside
179: * @param fromRx a regular expression to match against the source files
180: * (or null to keep the same relative file names); only one
181: * match (somewhere in the path) is checked for; failure to match
182: * prevents the file from being included
183: * @param toDir a target directory that results will reside in
184: * @param toSubst replacement text for <code>fromRx</code> (may include regexp references),
185: * or must be null if <code>fromRx</code> was null
186: * @param strict true to return null in case some starting files did not match any target file
187: * @return a list of corresponding target files (may be empty), or null if in strict mode
188: * and there was at least one source file which did not match a target file
189:
190: * @throws IllegalArgumentException in case some source file is not in the source directory
191: */
192: public static FileObject[] regexpMapFiles(FileObject[] fromFiles,
193: FileObject fromDir, Pattern fromRx, FileObject toDir,
194: String toSubst, boolean strict)
195: throws IllegalArgumentException {
196: List<FileObject> files = new ArrayList<FileObject>();
197: for (FileObject fromFile : fromFiles) {
198: String path = FileUtil.getRelativePath(fromDir, fromFile);
199: if (path == null) {
200: throw new IllegalArgumentException("The file "
201: + fromFile + " is not in " + fromDir); // NOI18N
202: }
203: String toPath;
204: if (fromRx != null) {
205: Matcher m = fromRx.matcher(path);
206: toPath = m.replaceFirst(toSubst);
207: if (toPath.equals(path) && !m.find(0)) {
208: // Did not match the pattern.
209: if (strict) {
210: return null;
211: } else {
212: continue;
213: }
214: }
215: } else {
216: toPath = path;
217: }
218: FileObject target = toDir.getFileObject(toPath);
219: if (target == null) {
220: if (strict) {
221: return null;
222: } else {
223: continue;
224: }
225: }
226: files.add(target);
227: }
228: return files.toArray(new FileObject[files.size()]);
229: }
230:
231: /**
232: * Create an "includes" string such as is accepted by many Ant commands
233: * as well as filesets.
234: * <samp>/</samp> is always used as the separator in the relative paths.
235: * @param files a list of files or folders to include, in the case of folder
236: * the generated include contains recursively all files under the folder.
237: * @param dir a directory in which all the files reside
238: * @return a comma-separated list of relative file paths suitable for use by Ant
239: * (the empty string in case there are no files)
240: * @throws IllegalArgumentException in case some file is not in the directory
241: */
242: public static String antIncludesList(FileObject[] files,
243: FileObject dir) throws IllegalArgumentException {
244: return antIncludesList(files, dir, true);
245: }
246:
247: /**
248: * Create an "includes" string such as is accepted by many Ant commands
249: * as well as filesets.
250: * <samp>/</samp> is always used as the separator in the relative paths.
251: * @param files a list of files or folders to include, in the case of folder
252: * the generated include contains recursively all files under the folder.
253: * @param dir a directory in which all the files reside
254: * @param recursive if true the include list for directory is recursive
255: * @return a comma-separated list of relative file paths suitable for use by Ant
256: * (the empty string in case there are no files)
257: * @throws IllegalArgumentException in case some file is not in the directory
258: * @since org.apache.tools.ant.module/3 3.16
259: */
260: public static String antIncludesList(FileObject[] files,
261: FileObject dir, boolean recursive)
262: throws IllegalArgumentException {
263: if (!dir.isFolder()) {
264: throw new IllegalArgumentException("Not a folder: " + dir); // NOI18N
265: }
266: StringBuffer b = new StringBuffer();
267: for (int i = 0; i < files.length; i++) {
268: String path = FileUtil.getRelativePath(dir, files[i]);
269: if (path == null) {
270: throw new IllegalArgumentException("The file "
271: + files[i] + " is not in " + dir); // NOI18N
272: }
273: if (i > 0) {
274: b.append(',');
275: }
276: b.append(path);
277: if (files[i].isFolder()) {
278: // files[i] == dir, cannot use "/".
279: if (path.length() > 0) {
280: b.append('/'); //NOI18N
281: }
282: b.append('*'); //NOI18N
283: if (recursive) {
284: b.append('*'); //NOI18N
285: }
286: }
287: }
288: return b.toString();
289: }
290:
291: }
|