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.modules.apisupport.project;
043:
044: import org.netbeans.modules.apisupport.project.spi.NbModuleProvider;
045: import java.io.File;
046: import java.io.IOException;
047: import java.io.InputStream;
048: import java.io.OutputStream;
049: import java.util.Enumeration;
050: import java.util.HashMap;
051: import java.util.Map;
052: import java.util.SortedSet;
053: import java.util.TreeSet;
054: import java.util.jar.JarEntry;
055: import java.util.jar.JarFile;
056: import org.netbeans.api.project.ProjectManager;
057: import org.netbeans.modules.apisupport.project.ui.customizer.SingleModuleProperties;
058: import org.netbeans.modules.apisupport.project.universe.LocalizedBundleInfo;
059: import org.netbeans.modules.apisupport.project.universe.ModuleList;
060: import org.netbeans.spi.project.support.ant.AntProjectHelper;
061: import org.netbeans.spi.project.support.ant.EditableProperties;
062: import org.netbeans.spi.project.support.ant.GeneratedFilesHelper;
063: import org.netbeans.spi.project.support.ant.PropertyUtils;
064: import org.openide.ErrorManager;
065: import org.openide.filesystems.FileLock;
066: import org.openide.filesystems.FileObject;
067: import org.openide.filesystems.FileUtil;
068: import org.openide.util.Mutex;
069: import org.openide.util.MutexException;
070: import org.openide.xml.XMLUtil;
071: import org.w3c.dom.Document;
072: import org.w3c.dom.Element;
073:
074: /**
075: * Servers for generating new NetBeans Modules templates.
076: *
077: * @author Martin Krauskopf
078: */
079: public class NbModuleProjectGenerator {
080:
081: public static final String PLATFORM_PROPERTIES_PATH = "nbproject/platform.properties"; // NOI18N
082:
083: /** Use static factory methods instead. */
084: private NbModuleProjectGenerator() {/* empty constructor*/
085: }
086:
087: /** Generates standalone NetBeans Module. */
088: public static void createStandAloneModule(final File projectDir,
089: final String cnb, final String name,
090: final String bundlePath, final String layerPath,
091: final String platformID) throws IOException {
092: try {
093: ProjectManager.mutex().writeAccess(
094: new Mutex.ExceptionAction<Void>() {
095: public Void run() throws IOException {
096: final FileObject dirFO = FileUtil
097: .createFolder(projectDir);
098: if (ProjectManager.getDefault()
099: .findProject(dirFO) != null) {
100: throw new IllegalArgumentException(
101: "Already a project in " + dirFO); // NOI18N
102: }
103: createProjectXML(dirFO, cnb,
104: NbModuleProvider.STANDALONE);
105: createPlatformProperties(dirFO, platformID);
106: createManifest(dirFO, cnb, bundlePath,
107: layerPath);
108: if (bundlePath != null) {
109: createBundle(dirFO, bundlePath, name);
110: }
111: if (layerPath != null) {
112: createLayerInSrc(dirFO, layerPath);
113: }
114: createEmptyTestDir(dirFO);
115: createInitialProperties(dirFO);
116: ModuleList.refresh();
117: ProjectManager.getDefault()
118: .clearNonProjectCache();
119: return null;
120: }
121: });
122: } catch (MutexException e) {
123: throw (IOException) e.getException();
124: }
125: }
126:
127: /** Generates suite component NetBeans Module. */
128: public static void createSuiteComponentModule(
129: final File projectDir, final String cnb, final String name,
130: final String bundlePath, final String layerPath,
131: final File suiteDir) throws IOException {
132: try {
133: ProjectManager.mutex().writeAccess(
134: new Mutex.ExceptionAction<Void>() {
135: public Void run() throws IOException {
136: final FileObject dirFO = FileUtil
137: .createFolder(projectDir);
138: if (ProjectManager.getDefault()
139: .findProject(dirFO) != null) {
140: throw new IllegalArgumentException(
141: "Already a project in " + dirFO); // NOI18N
142: }
143: createProjectXML(dirFO, cnb,
144: NbModuleProvider.SUITE_COMPONENT);
145: createSuiteProperties(dirFO, suiteDir);
146: createManifest(dirFO, cnb, bundlePath,
147: layerPath);
148: if (bundlePath != null) {
149: createBundle(dirFO, bundlePath, name);
150: }
151: if (layerPath != null) {
152: createLayerInSrc(dirFO, layerPath);
153: }
154: createEmptyTestDir(dirFO);
155: createInitialProperties(dirFO);
156: ModuleList.refresh();
157: ProjectManager.getDefault()
158: .clearNonProjectCache();
159: appendToSuite(cnb, dirFO, suiteDir);
160: return null;
161: }
162: });
163: } catch (MutexException e) {
164: throw (IOException) e.getException();
165: }
166: }
167:
168: /** Generates suite component Library Wrapper NetBeans Module. */
169: public static void createSuiteLibraryModule(final File projectDir,
170: final String cnb, final String name,
171: final String bundlePath, final File suiteDir,
172: final File license, final File[] jars) throws IOException {
173: try {
174: ProjectManager.mutex().writeAccess(
175: new Mutex.ExceptionAction<Void>() {
176: public Void run() throws IOException {
177: final FileObject dirFO = FileUtil
178: .createFolder(projectDir);
179: if (ProjectManager.getDefault()
180: .findProject(dirFO) != null) {
181: throw new IllegalArgumentException(
182: "Already a project in " + dirFO); // NOI18N
183: }
184:
185: EditableProperties props = new EditableProperties(
186: true);
187: props.put(
188: SingleModuleProperties.IS_AUTOLOAD,
189: "true"); // NOI18N
190: SortedSet<String> packageList = new TreeSet<String>();
191: Map<String, String> classPathExtensions = new HashMap<String, String>();
192:
193: File releaseDir = new File(projectDir,
194: "release/modules/ext"); //NOI18N
195: if (!releaseDir.mkdirs()) {
196: //TODO report error
197: Util.err
198: .log("cannot create release directory.");
199: }
200: FileObject relDirFo = FileUtil
201: .toFileObject(releaseDir);
202: for (int i = 0; i < jars.length; i++) {
203: FileObject orig = FileUtil
204: .toFileObject(FileUtil
205: .normalizeFile(jars[i]));
206: if (orig != null) {
207: JarFile jf = null;
208: try {
209: FileUtil.copyFile(orig,
210: relDirFo, orig
211: .getName());
212: jf = new JarFile(jars[i]);
213: Enumeration en = jf.entries();
214: while (en.hasMoreElements()) {
215: JarEntry entry = (JarEntry) en
216: .nextElement();
217: if (!entry.isDirectory()
218: && entry
219: .getName()
220: .endsWith(
221: ".class")) { // NOI18N
222: String nm = entry
223: .getName();
224: if (!Util
225: .isValidJavaFQN(nm
226: .substring(
227: 0,
228: nm
229: .length() - 6)
230: .replace(
231: '/',
232: '.'))) {
233: continue; // #72669
234: }
235: int index = nm
236: .lastIndexOf('/');
237: if (index > -1) {
238: String path = nm
239: .substring(
240: 0,
241: index);
242: packageList
243: .add(path
244: .replace(
245: '/',
246: '.'));
247: }
248: }
249: }
250: classPathExtensions
251: .put(
252: "ext/"
253: + orig
254: .getNameExt(),
255: "release/modules/ext/"
256: + orig
257: .getNameExt()); // NOI18N
258: } catch (IOException e) {
259: //TODO report
260: Util.err.notify(e);
261: } finally {
262: if (jf != null) {
263: try {
264: jf.close();
265: } catch (IOException e) {
266: Util.err
267: .notify(
268: ErrorManager.INFORMATIONAL,
269: e);
270: }
271: }
272: }
273: }
274: }
275:
276: if (license != null && license.exists()) {
277: FileObject fo = FileUtil
278: .toFileObject(license);
279: try {
280: FileUtil.copyFile(fo, dirFO, fo
281: .getName());
282: props
283: .put(
284: SingleModuleProperties.LICENSE_FILE,
285: "${basedir}/"
286: + fo
287: .getNameExt()); // NOI18N
288: //TODO set the nbm.license property
289: } catch (IOException e) {
290: //TODO report
291: Util.err.notify(e);
292: }
293:
294: }
295: ProjectXMLManager
296: .generateLibraryModuleTemplate(
297: createFileObject(
298: dirFO,
299: AntProjectHelper.PROJECT_XML_PATH),
300: cnb,
301: NbModuleProvider.SUITE_COMPONENT,
302: packageList,
303: classPathExtensions);
304: createSuiteProperties(dirFO, suiteDir);
305: createManifest(dirFO, cnb, bundlePath, null);
306: createBundle(dirFO, bundlePath, name);
307:
308: // write down the nbproject/properties file
309: FileObject bundleFO = createFileObject(
310: dirFO,
311: "nbproject/project.properties"); // NOI18N
312: Util.storeProperties(bundleFO, props);
313:
314: ModuleList.refresh();
315: ProjectManager.getDefault()
316: .clearNonProjectCache();
317: appendToSuite(cnb, dirFO, suiteDir);
318: return null;
319: }
320: });
321: } catch (MutexException e) {
322: throw (IOException) e.getException();
323: }
324: }
325:
326: /**
327: * Generates NetBeans Module within the netbeans.org source tree.
328: */
329: public static void createNetBeansOrgModule(final File projectDir,
330: final String cnb, final String name,
331: final String bundlePath, final String layerPath)
332: throws IOException {
333: try {
334: ProjectManager.mutex().writeAccess(
335: new Mutex.ExceptionAction<Void>() {
336: public Void run() throws IOException {
337: File nborg = ModuleList
338: .findNetBeansOrg(projectDir);
339: if (nborg == null) {
340: throw new IllegalArgumentException(
341: projectDir
342: + " doesn't "
343: + // NOI18N
344: "point to a top-level directory within the netbeans.org main or contrib repositories"); // NOI18N
345: }
346: final FileObject dirFO = FileUtil
347: .createFolder(projectDir);
348: if (ProjectManager.getDefault()
349: .findProject(dirFO) != null) {
350: throw new IllegalArgumentException(
351: "Already a project in " + dirFO); // NOI18N
352: }
353: createNetBeansOrgBuildXML(dirFO, cnb, nborg);
354: createProjectXML(dirFO, cnb,
355: NbModuleProvider.NETBEANS_ORG);
356: createManifest(dirFO, cnb, bundlePath,
357: layerPath);
358: createBundle(dirFO, bundlePath, name);
359: if (layerPath != null) {
360: createLayerInSrc(dirFO, layerPath);
361: }
362: createEmptyTestDir(dirFO);
363: createInitialProperties(dirFO);
364: ModuleList.refresh();
365: ProjectManager.getDefault()
366: .clearNonProjectCache();
367: return null;
368: }
369: });
370: } catch (MutexException e) {
371: throw (IOException) e.getException();
372: }
373: }
374:
375: /**
376: * Creates basic <em>nbbuild/project.xml</em> or whatever
377: * <code>AntProjectHelper.PROJECT_XML_PATH</code> is pointing to for
378: * <em>standalone</em> or <em>module in suite</em> module.
379: */
380: private static void createProjectXML(FileObject projectDir,
381: String cnb, NbModuleProvider.NbModuleType type)
382: throws IOException {
383: ProjectXMLManager.generateEmptyModuleTemplate(createFileObject(
384: projectDir, AntProjectHelper.PROJECT_XML_PATH), cnb,
385: type);
386: }
387:
388: /**
389: * Creates basic <em>build.xml</em> or whatever
390: * <code>GeneratedFilesHelper.BUILD_XML_PATH</code> is pointing to.
391: */
392: private static void createNetBeansOrgBuildXML(
393: FileObject projectDir, String cnb, File nborg)
394: throws IOException {
395: FileObject buildScript = NbModuleProjectGenerator
396: .createFileObject(projectDir,
397: GeneratedFilesHelper.BUILD_XML_PATH);
398: Document prjDoc = XMLUtil.createDocument("project", null, null,
399: null); // NOI18N
400: Element prjEl = prjDoc.getDocumentElement();
401: prjEl.setAttribute("name", PropertyUtils.relativizeFile(nborg, // NOI18N
402: FileUtil.toFile(projectDir)));
403: prjEl.setAttribute("default", "netbeans"); // NOI18N
404: prjEl.setAttribute("basedir", "."); // NOI18N
405:
406: Element el = prjDoc.createElement("description"); // NOI18N
407: el.appendChild(prjDoc
408: .createTextNode("Builds, tests, and runs the " + // NOI18N
409: "project " + cnb)); // NOI18N
410: prjEl.appendChild(el);
411:
412: el = prjDoc.createElement("import"); // NOI18N
413: el.setAttribute("file", PropertyUtils.relativizeFile(FileUtil
414: .toFile(projectDir), // NOI18N
415: new File(nborg, "nbbuild/templates/projectized.xml"))); // NOI18N
416: prjEl.appendChild(el);
417:
418: // store document to disk
419: FileLock lock = buildScript.lock();
420: try {
421: OutputStream os = buildScript.getOutputStream(lock);
422: try {
423: XMLUtil.write(prjDoc, os, "UTF-8"); // NOI18N
424: } finally {
425: os.close();
426: }
427: } finally {
428: lock.releaseLock();
429: }
430: }
431:
432: /**
433: * Detects whether <code>projectDir</code> is relative to
434: * <code>suiteDir</code> and creates <em>nbproject/suite.properties</em> or
435: * <em>nbproject/private/suite-private.properties</em> with
436: * <em>suite.dir</em> appropriately set.
437: */
438: public static void createSuiteProperties(FileObject projectDir,
439: File suiteDir) throws IOException {
440: File projectDirF = FileUtil.toFile(projectDir);
441: String suiteLocation;
442: String suitePropertiesLocation;
443: //mkleint: removed CollocationQuery.areCollocated() reference
444: // when AlwaysRelativeCQI gets removed the condition resolves to false more frequently.
445: // that might not be desirable.
446: String rel = PropertyUtils
447: .relativizeFile(projectDirF, suiteDir);
448: if (rel != null) {
449: suiteLocation = "${basedir}/" + rel; // NOI18N
450: suitePropertiesLocation = "nbproject/suite.properties"; // NOI18N
451: } else {
452: suiteLocation = suiteDir.getAbsolutePath();
453: suitePropertiesLocation = "nbproject/private/suite-private.properties"; // NOI18N
454: }
455: EditableProperties props = new EditableProperties(true);
456: props.setProperty("suite.dir", suiteLocation); // NOI18N
457: FileObject suiteProperties = createFileObject(projectDir,
458: suitePropertiesLocation);
459: Util.storeProperties(suiteProperties, props);
460: }
461:
462: /**
463: * Appends currently created project in the <code>projectDir<code> to a
464: * suite project contained in the <code>suiteDir</code>. Also intelligently
465: * decides whether an added project is relative to a destination suite or
466: * absolute and uses either <em>nbproject/project.properties</em> or
467: * <em>nbproject/private/private.properties</em> appropriately.
468: */
469: private static void appendToSuite(String cnb,
470: FileObject projectDir, File suiteDir) throws IOException {
471: File projectDirF = FileUtil.toFile(projectDir);
472: File suiteGlobalPropsFile = new File(suiteDir,
473: "nbproject/project.properties"); // NOI18N
474: FileObject suiteGlobalPropFO;
475: if (suiteGlobalPropsFile.exists()) {
476: suiteGlobalPropFO = FileUtil
477: .toFileObject(suiteGlobalPropsFile);
478: } else {
479: suiteGlobalPropFO = createFileObject(suiteGlobalPropsFile);
480: }
481: EditableProperties globalProps = Util
482: .loadProperties(suiteGlobalPropFO);
483: String projectPropKey = "project." + cnb; // NOI18N
484: String rel = PropertyUtils
485: .relativizeFile(suiteDir, projectDirF);
486: //mkleint: removed CollocationQuery.areCollocated() reference
487: // when AlwaysRelativeCQI gets removed the condition resolves to false more frequently.
488: // that might not be desirable.
489: if (rel != null) {
490: globalProps.setProperty(projectPropKey, rel);
491: } else {
492: File suitePrivPropsFile = new File(suiteDir,
493: "nbproject/private/private.properties"); // NOI18N
494: FileObject suitePrivPropFO;
495: if (suitePrivPropsFile.exists()) {
496: suitePrivPropFO = FileUtil
497: .toFileObject(suitePrivPropsFile);
498: } else {
499: suitePrivPropFO = createFileObject(suitePrivPropsFile);
500: }
501: EditableProperties privProps = Util
502: .loadProperties(suitePrivPropFO);
503: privProps.setProperty(projectPropKey, projectDirF
504: .getAbsolutePath());
505: Util.storeProperties(suitePrivPropFO, privProps);
506: }
507: String modulesProp = globalProps.getProperty("modules"); // NOI18N
508: if (modulesProp == null) {
509: modulesProp = "";
510: }
511: if (modulesProp.length() > 0) {
512: modulesProp += ":"; // NOI18N
513: }
514: modulesProp += "${" + projectPropKey + "}"; // NOI18N
515: globalProps.setProperty("modules", modulesProp.split("(?<=:)",
516: -1)); // NOI18N
517: Util.storeProperties(suiteGlobalPropFO, globalProps);
518: }
519:
520: private static void createPlatformProperties(FileObject projectDir,
521: String platformID) throws IOException {
522: FileObject plafPropsFO = createFileObject(projectDir,
523: NbModuleProjectGenerator.PLATFORM_PROPERTIES_PATH);
524: EditableProperties props = new EditableProperties(true);
525: props.put("nbplatform.active", platformID); // NOI18N
526: Util.storeProperties(plafPropsFO, props);
527: }
528:
529: private static void createManifest(FileObject projectDir,
530: String cnb, String bundlePath, String layerPath)
531: throws IOException {
532: FileObject manifestFO = createFileObject(projectDir,
533: "manifest.mf"); // NOI18N
534: ManifestManager.createManifest(manifestFO, cnb, "1.0",
535: bundlePath, layerPath); // NOI18N
536: }
537:
538: private static void createBundle(FileObject projectDir,
539: String bundlePath, String name) throws IOException {
540: String pathToBundle = "src/" + bundlePath.replace('\\', '/'); // NOI18N
541: FileObject bundleFO = createFileObject(projectDir, pathToBundle);
542: EditableProperties props = new EditableProperties(true);
543: props.put(LocalizedBundleInfo.NAME, name);
544: Util.storeProperties(bundleFO, props);
545: }
546:
547: private static void createLayerInSrc(FileObject projectDir,
548: String layerPath) throws IOException {
549: createLayer(projectDir, "src/" + layerPath); // NOI18N
550: }
551:
552: public static FileObject createLayer(FileObject projectDir,
553: String layerPath) throws IOException {
554: FileObject layerFO = createFileObject(projectDir, layerPath); // NOI18N
555: FileLock lock = layerFO.lock();
556: try {
557: InputStream is = NbModuleProjectGenerator.class
558: .getResourceAsStream("ui/resources/layer_template.xml"); // NOI18N
559: try {
560: OutputStream os = layerFO.getOutputStream(lock);
561: try {
562: FileUtil.copy(is, os);
563: } finally {
564: os.close();
565: }
566: } finally {
567: is.close();
568: }
569: } finally {
570: lock.releaseLock();
571: }
572: return layerFO;
573: }
574:
575: private static void createEmptyTestDir(FileObject projectDir)
576: throws IOException {
577: FileUtil.createFolder(projectDir, "test/unit/src"); // NOI18N
578: }
579:
580: private static void createInitialProperties(FileObject projectDir)
581: throws IOException {
582: EditableProperties props = new EditableProperties();
583: props.put(SingleModuleProperties.JAVAC_SOURCE, "1.5"); // NOI18N
584: props.put(SingleModuleProperties.JAVAC_COMPILERARGS,
585: "-Xlint -Xlint:-serial"); // NOI18N
586: FileObject f = createFileObject(projectDir,
587: AntProjectHelper.PROJECT_PROPERTIES_PATH);
588: Util.storeProperties(f, props);
589: }
590:
591: /**
592: * Creates a new <code>FileObject</code>.
593: * Throws <code>IllegalArgumentException</code> if such an object already
594: * exists. Throws <code>IOException</code> if creation fails.
595: */
596: private static FileObject createFileObject(FileObject dir,
597: String relToDir) throws IOException {
598: FileObject createdFO = dir.getFileObject(relToDir);
599: if (createdFO != null) {
600: throw new IllegalArgumentException("File " + createdFO
601: + " already exists."); // NOI18N
602: }
603: createdFO = FileUtil.createData(dir, relToDir);
604: return createdFO;
605: }
606:
607: /**
608: * Creates a new <code>FileObject</code>.
609: * Throws <code>IllegalArgumentException</code> if such an object already
610: * exists. Throws <code>IOException</code> if creation fails.
611: */
612: private static FileObject createFileObject(File fileToCreate)
613: throws IOException {
614: File parent = fileToCreate.getParentFile();
615: if (parent == null) {
616: throw new IllegalArgumentException("Cannot create: "
617: + fileToCreate); // NOI18N
618: }
619: if (!parent.exists()) {
620: parent.mkdirs();
621: }
622: return createFileObject(FileUtil.toFileObject(parent),
623: fileToCreate.getName());
624: }
625:
626: }
|