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.spi.project.support.ant;
043:
044: import org.netbeans.spi.project.ant.AntBuildExtenderFactory;
045: import org.netbeans.spi.project.ant.AntBuildExtenderImplementation;
046: import java.beans.PropertyChangeEvent;
047: import java.beans.PropertyChangeListener;
048: import java.io.File;
049: import java.io.FileInputStream;
050: import java.io.FileOutputStream;
051: import java.io.IOException;
052: import java.io.InputStream;
053: import java.io.InputStreamReader;
054: import java.io.OutputStream;
055: import java.io.OutputStreamWriter;
056: import java.io.Reader;
057: import java.io.Writer;
058: import java.net.URI;
059: import java.net.URISyntaxException;
060: import java.net.URL;
061: import java.util.ArrayList;
062: import java.util.List;
063: import java.util.Map;
064: import java.util.Properties;
065: import java.util.regex.Matcher;
066: import java.util.regex.Pattern;
067: import java.util.regex.PatternSyntaxException;
068: import javax.swing.Icon;
069: import javax.swing.event.ChangeListener;
070: import org.netbeans.api.diff.Difference;
071: import org.netbeans.api.project.Project;
072: import org.netbeans.api.project.ant.AntArtifact;
073: import org.netbeans.modules.diff.builtin.provider.BuiltInDiffProvider;
074: import org.netbeans.modules.project.ant.Util;
075: import org.netbeans.spi.diff.DiffProvider;
076: import org.netbeans.spi.project.AuxiliaryConfiguration;
077: import org.netbeans.api.project.ProjectInformation;
078: import org.netbeans.api.project.ProjectManager;
079: import org.netbeans.api.project.ant.AntBuildExtender;
080: import org.netbeans.spi.project.ant.AntArtifactProvider;
081: import org.netbeans.spi.queries.CollocationQueryImplementation;
082: import org.openide.filesystems.FileObject;
083: import org.openide.util.ChangeSupport;
084: import org.openide.util.Lookup;
085: import org.openide.util.lookup.Lookups;
086: import org.openide.xml.XMLUtil;
087: import org.w3c.dom.Document;
088: import org.w3c.dom.Element;
089: import org.xml.sax.InputSource;
090: import org.xml.sax.SAXException;
091:
092: /**
093: * Test-related utilities for use in ant/project.
094: * @author Jesse Glick
095: */
096: public class AntBasedTestUtil {
097:
098: private AntBasedTestUtil() {
099: }
100:
101: /**
102: * Create an AntBasedProjectType instance suitable for testing.
103: * It has the type code <samp>test</samp>.
104: * It uses <samp><data></samp> as the configuration data element,
105: * with namespaces <samp>urn:test:shared</samp> and <samp>urn:test:private</samp>.
106: * Loading the project succeeds unless there is a file in it <samp>nbproject/broken</samp>.
107: * The project's methods mostly delegate to the helper; its lookup uses the helper's
108: * supports for AuxiliaryConfiguration, CacheDirectoryProvider, and SubprojectProvider,
109: * and also adds an instance of String, namely "hello".
110: * It also puts the AntProjectHelper into its lookup to assist in testing.
111: * <code>build-impl.xml</code> is generated from <code>data/build-impl.xsl</code>
112: * by a ProjectXmlSavedHook using GeneratedFilesHelper.refreshBuildScript.
113: * A ReferenceHelper is also added to its lookup for testing purposes.
114: * An {@link AntArtifactProviderMutable} is added which initially publishes two artifacts:
115: * one of target 'dojar' type 'jar' with artifact ${build.jar};
116: * one of target 'dojavadoc' type 'javadoc' with artifact ${build.javadoc};
117: * both using clean target 'clean'.
118: * A GeneratedFilesHelper is added to its lookup for testing purposes.
119: * @return a project type object for testing purposes
120: */
121: public static AntBasedProjectType testAntBasedProjectType() {
122: return new TestAntBasedProjectType();
123: }
124:
125: public static AntBasedProjectType testAntBasedProjectType(
126: AntBuildExtenderImplementation extender) {
127: return new TestAntBasedProjectType(extender);
128: }
129:
130: /**
131: * You can adjust which artifacts are supplied.
132: */
133: public interface AntArtifactProviderMutable extends
134: AntArtifactProvider {
135: void setBuildArtifacts(AntArtifact[] arts);
136: }
137:
138: private static final class TestAntBasedProjectType implements
139: AntBasedProjectType {
140: private AntBuildExtenderImplementation ext;
141:
142: TestAntBasedProjectType() {
143: }
144:
145: TestAntBasedProjectType(AntBuildExtenderImplementation ext) {
146: this .ext = ext;
147: }
148:
149: public String getType() {
150: return "test";
151: }
152:
153: public Project createProject(AntProjectHelper helper)
154: throws IOException {
155: return new TestAntBasedProject(helper, ext);
156: }
157:
158: public String getPrimaryConfigurationDataElementName(
159: boolean shared) {
160: return "data";
161: }
162:
163: public String getPrimaryConfigurationDataElementNamespace(
164: boolean shared) {
165: return shared ? "urn:test:shared" : "urn:test:private";
166: }
167:
168: }
169:
170: private static final class TestAntBasedProject implements Project {
171:
172: private final AntProjectHelper helper;
173: private final ReferenceHelper refHelper;
174: private final GeneratedFilesHelper genFilesHelper;
175: private final Lookup l;
176:
177: TestAntBasedProject(AntProjectHelper helper,
178: AntBuildExtenderImplementation ext) throws IOException {
179: if (helper.getProjectDirectory().getFileObject(
180: "nbproject/broken") != null) {
181: throw new IOException("broken");
182: }
183: this .helper = helper;
184: AuxiliaryConfiguration aux = helper
185: .createAuxiliaryConfiguration();
186: refHelper = new ReferenceHelper(helper, aux, helper
187: .getStandardPropertyEvaluator());
188: Object extContent;
189: if (ext != null) {
190: AntBuildExtender e = AntBuildExtenderFactory
191: .createAntExtender(ext);
192: genFilesHelper = new GeneratedFilesHelper(helper, e);
193: extContent = e;
194: } else {
195: genFilesHelper = new GeneratedFilesHelper(helper);
196: extContent = new Object();
197: }
198: l = Lookups.fixed(new Object[] {
199: new TestInfo(),
200: helper,
201: refHelper,
202: genFilesHelper,
203: aux,
204: helper.createCacheDirectoryProvider(),
205: helper.createSharabilityQuery(helper
206: .getStandardPropertyEvaluator(),
207: new String[0], new String[0]),
208: refHelper.createSubprojectProvider(),
209: new TestAntArtifactProvider(),
210: new ProjectXmlSavedHook() {
211: protected void projectXmlSaved()
212: throws IOException {
213: genFilesHelper
214: .refreshBuildScript(
215: GeneratedFilesHelper.BUILD_IMPL_XML_PATH,
216: AntBasedTestUtil.class
217: .getResource("data/build-impl.xsl"),
218: false);
219: genFilesHelper
220: .refreshBuildScript(
221: GeneratedFilesHelper.BUILD_XML_PATH,
222: testBuildXmlStylesheet(),
223: false);
224: }
225: }, "hello", extContent });
226: }
227:
228: public FileObject getProjectDirectory() {
229: return helper.getProjectDirectory();
230: }
231:
232: public Lookup getLookup() {
233: return l;
234: }
235:
236: public String toString() {
237: return "TestAntBasedProject[" + getProjectDirectory() + "]";
238: }
239:
240: private final class TestInfo implements ProjectInformation {
241:
242: TestInfo() {
243: }
244:
245: private String getText(String elementName) {
246: Element data = helper.getPrimaryConfigurationData(true);
247: Element el = Util.findElement(data, elementName,
248: "urn:test:shared");
249: if (el != null) {
250: String text = Util.findText(el);
251: if (text != null) {
252: return text;
253: }
254: }
255: // Some kind of fallback here.
256: return getProjectDirectory().getNameExt();
257: }
258:
259: public String getName() {
260: return getText("name");
261: }
262:
263: public String getDisplayName() {
264: return getText("display-name");
265: }
266:
267: public Icon getIcon() {
268: return null;
269: }
270:
271: public Project getProject() {
272: return TestAntBasedProject.this ;
273: }
274:
275: public void addPropertyChangeListener(
276: PropertyChangeListener listener) {
277: }
278:
279: public void removePropertyChangeListener(
280: PropertyChangeListener listener) {
281: }
282:
283: }
284:
285: private final class TestAntArtifactProvider implements
286: AntArtifactProviderMutable {
287:
288: private AntArtifact[] arts;
289:
290: TestAntArtifactProvider() {
291: }
292:
293: public AntArtifact[] getBuildArtifacts() {
294: if (arts != null) {
295: return arts;
296: }
297: URI[] uris = null;
298: try {
299: uris = new URI[] { new URI("dist/foo.jar"),
300: new URI("dist/bar.jar") };
301: } catch (URISyntaxException ex) {
302: ex.printStackTrace();
303: }
304: return new AntArtifact[] {
305: helper.createSimpleAntArtifact("jar",
306: "build.jar",
307: helper.getStandardPropertyEvaluator(),
308: "dojar", "clean"),
309: helper.createSimpleAntArtifact("javadoc",
310: "build.javadoc",
311: helper.getStandardPropertyEvaluator(),
312: "dojavadoc", "clean"),
313: new TestAntArtifact(uris, helper), };
314: }
315:
316: public void setBuildArtifacts(AntArtifact[] arts) {
317: this .arts = arts;
318: }
319:
320: }
321:
322: }
323:
324: /**
325: * Load a properties file from disk.
326: * @param h a project reference
327: * @param path the relative file path
328: * @return properties available at that location, or null if no such file
329: * @throws IOException if there is any problem loading it
330: */
331: public static Properties slurpProperties(AntProjectHelper h,
332: String path) throws IOException {
333: Properties p = new Properties();
334: File f = h.resolveFile(path);
335: if (!f.isFile()) {
336: return null;
337: }
338: InputStream is = new FileInputStream(f);
339: try {
340: p.load(is);
341: } finally {
342: is.close();
343: }
344: return p;
345: }
346:
347: /**
348: * Load an XML file from disk.
349: * @param h a project reference
350: * @param path the relative file path
351: * @return an XML document available at that location, or null if no such file
352: * @throws IOException if there is any problem loading it
353: * @throws SAXException if it is malformed
354: */
355: public static Document slurpXml(AntProjectHelper h, String path)
356: throws IOException, SAXException {
357: File f = h.resolveFile(path);
358: if (!f.isFile()) {
359: return null;
360: }
361: return XMLUtil.parse(new InputSource(f.toURI().toString()),
362: false, true, Util.defaultErrorHandler(), null);
363: }
364:
365: /**
366: * Load a text file from disk.
367: * Assumes UTF-8 encoding.
368: * @param h a project reference
369: * @param path the relative file path
370: * @return the raw contents of the text file at that point, or null if no such file
371: * @throws IOException if there is any problem loading it
372: */
373: public static String slurpText(AntProjectHelper h, String path)
374: throws IOException {
375: File f = h.resolveFile(path);
376: if (!f.isFile()) {
377: return null;
378: }
379: InputStream is = new FileInputStream(f);
380: try {
381: Reader r = new InputStreamReader(is, "UTF-8");
382: StringBuffer b = new StringBuffer();
383: char[] buf = new char[4096];
384: int read;
385: while ((read = r.read(buf)) != -1) {
386: b.append(buf, 0, read);
387: }
388: return b.toString();
389: } finally {
390: is.close();
391: }
392: }
393:
394: /**
395: * Get a sample <code>build.xsl</code>.
396: * @return a URL to a stylesheet
397: */
398: public static URL testBuildXmlStylesheet() {
399: return AntBasedTestUtil.class.getResource("data/build.xsl");
400: }
401:
402: /**
403: * A sample listener that just collects events it receives.
404: */
405: public static final class TestListener implements
406: AntProjectListener {
407:
408: private final List<AntProjectEvent> events = new ArrayList<AntProjectEvent>();
409:
410: /** Create a new listener. */
411: public TestListener() {
412: }
413:
414: /**
415: * Get a list of received events, in order.
416: * Also clears the list for the next call.
417: * @return an ordered list of Ant project events
418: */
419: public AntProjectEvent[] events() {
420: AntProjectEvent[] evs = events
421: .toArray(new AntProjectEvent[0]);
422: events.clear();
423: return evs;
424: }
425:
426: public void configurationXmlChanged(AntProjectEvent ev) {
427: assert ev.getPath().equals(
428: AntProjectHelper.PROJECT_XML_PATH)
429: || ev.getPath().equals(
430: AntProjectHelper.PRIVATE_XML_PATH);
431: events.add(ev);
432: }
433:
434: public void propertiesChanged(AntProjectEvent ev) {
435: assert !ev.getPath().equals(
436: AntProjectHelper.PROJECT_XML_PATH)
437: && !ev.getPath().equals(
438: AntProjectHelper.PRIVATE_XML_PATH);
439: events.add(ev);
440: }
441:
442: }
443:
444: /**
445: * Count the number of (line-based) differences between two text files.
446: * The returned count has, in this order:
447: * <ol>
448: * <li>Lines modified between the first and second files.
449: * <li>Lines added in the second file that were not in the first.
450: * <li>Lines removed in the second file that were in the first.
451: * </ol>
452: * It thus serves as a summary of the number of diff lines you would expect
453: * to get from e.g. a version control system doing a normal text checkin.
454: * @param r1 the first file (the reader will not be closed for you)
455: * @param r2 the second file (the reader will not be closed for you)
456: * @return a count of lines modified, added, and removed (resp.)
457: * @throws IOException in case reading from the files failed
458: */
459: public static int[] countTextDiffs(Reader r1, Reader r2)
460: throws IOException {
461: DiffProvider dp = new BuiltInDiffProvider();
462: Difference[] diffs = dp.computeDiff(r1, r2);
463: int[] count = new int[3];
464: for (int i = 0; i < diffs.length; i++) {
465: switch (diffs[i].getType()) {
466: case Difference.CHANGE:
467: count[0] += Math.max(diffs[i].getFirstEnd()
468: - diffs[i].getFirstStart() + 1, diffs[i]
469: .getSecondEnd()
470: - diffs[i].getSecondStart() + 1);
471: break;
472: case Difference.ADD:
473: count[1] += (diffs[i].getSecondEnd()
474: - diffs[i].getSecondStart() + 1);
475: break;
476: case Difference.DELETE:
477: count[2] += (diffs[i].getFirstEnd()
478: - diffs[i].getFirstStart() + 1);
479: break;
480: default:
481: assert false : diffs[i];
482: }
483: }
484: return count;
485: }
486:
487: /**
488: * Get a sample file collocation query provider.
489: * Files under the supplied root are normally considered to be collocated.
490: * However the subdirectory <samp>separate</samp> (if it exists) forms its own root.
491: * And the subdirectory <samp>transient</samp> (if it exists) does not form a root,
492: * but any files in there are not considered collocated with anything.
493: */
494: public static CollocationQueryImplementation testCollocationQueryImplementation(
495: File root) {
496: return new TestCollocationQueryImplementation(root);
497: }
498:
499: private static final class TestCollocationQueryImplementation
500: implements CollocationQueryImplementation {
501:
502: private final File root;
503: private final String rootPath;
504: private final File separate;
505: private final String separatePath;
506: private final String transientPath;
507:
508: TestCollocationQueryImplementation(File root) {
509: this .root = root;
510: rootPath = root.getAbsolutePath();
511: separate = new File(root, "separate");
512: separatePath = separate.getAbsolutePath();
513: transientPath = new File(root, "transient")
514: .getAbsolutePath();
515: }
516:
517: public boolean areCollocated(File file1, File file2) {
518: File root1 = findRoot(file1);
519: if (root1 == null) {
520: return false;
521: } else {
522: return root1.equals(findRoot(file2));
523: }
524: }
525:
526: public File findRoot(File file) {
527: String path = file.getAbsolutePath();
528: if (!path.startsWith(rootPath)) {
529: return null;
530: }
531: if (path.startsWith(separatePath)) {
532: return separate;
533: }
534: if (path.startsWith(transientPath)) {
535: return null;
536: }
537: return root;
538: }
539:
540: @Override
541: public String toString() {
542: return "TestCollocationQueryImplementation[" + root + "]";
543: }
544:
545: }
546:
547: /**
548: * Replace all occurrences of a given string in a file with a new string.
549: * UTF-8 encoding is assumed.
550: * @param f the file to modify
551: * @param from the search string
552: * @param to the replacement string
553: * @return a count of how many occurrences were replaced
554: * @throws IOException in case reading or writing the file failed
555: */
556: public static int replaceInFile(File f, String from, String to)
557: throws IOException {
558: StringBuffer b = new StringBuffer((int) f.length());
559: InputStream is = new FileInputStream(f);
560: try {
561: Reader r = new InputStreamReader(is, "UTF-8");
562: char[] buf = new char[4096];
563: int i;
564: while ((i = r.read(buf)) != -1) {
565: b.append(buf, 0, i);
566: }
567: } finally {
568: is.close();
569: }
570: String s = b.toString();
571: String rx = "\\Q" + from + "\\E";
572: Pattern patt;
573: try {
574: patt = Pattern.compile(rx);
575: } catch (PatternSyntaxException e) {
576: assert false : e;
577: return -1;
578: }
579: Matcher m = patt.matcher(s);
580: int count = 0;
581: while (m.find()) {
582: count++;
583: }
584: String s2 = s.replaceAll(rx, to);
585: assert s2.length() - s.length() == count
586: * (to.length() - from.length());
587: OutputStream os = new FileOutputStream(f);
588: try {
589: Writer w = new OutputStreamWriter(os, "UTF-8");
590: w.write(s2);
591: w.flush();
592: } finally {
593: os.close();
594: }
595: return count;
596: }
597:
598: public static class TestAntArtifact extends AntArtifact {
599:
600: private URI[] uris;
601: private Project p;
602: private AntProjectHelper h;
603:
604: public TestAntArtifact(URI[] uris, AntProjectHelper h) {
605: this .uris = uris;
606: try {
607: this .p = ProjectManager.getDefault().findProject(
608: h.getProjectDirectory());
609: } catch (Exception e) {
610: e.printStackTrace();
611: }
612: this .h = h;
613: }
614:
615: public String getType() {
616: return "multi-jar"; // NOI18N
617: }
618:
619: public File getScriptLocation() {
620: return h.resolveFile(GeneratedFilesHelper.BUILD_XML_PATH);
621: }
622:
623: public String getTargetName() {
624: return "build"; // NOI18N
625: }
626:
627: public String getCleanTargetName() {
628: return "clean"; // NOI18N
629: }
630:
631: public URI[] getArtifactLocations() {
632: return uris;
633: }
634:
635: public Project getProject() {
636: return p;
637: }
638:
639: }
640:
641: public static final class TestMutablePropertyProvider implements
642: PropertyProvider {
643:
644: public final Map<String, String> defs;
645: private final ChangeSupport cs = new ChangeSupport(this );
646:
647: public TestMutablePropertyProvider(Map<String, String> defs) {
648: this .defs = defs;
649: }
650:
651: public void mutated() {
652: cs.fireChange();
653: }
654:
655: public Map<String, String> getProperties() {
656: return defs;
657: }
658:
659: public void addChangeListener(ChangeListener l) {
660: cs.addChangeListener(l);
661: }
662:
663: public void removeChangeListener(ChangeListener l) {
664: cs.removeChangeListener(l);
665: }
666:
667: }
668:
669: }
|