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-2007 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: package org.netbeans.modules.j2ee.ejbjarproject.classpath;
042:
043: import java.beans.PropertyChangeEvent;
044: import java.io.File;
045: import java.net.MalformedURLException;
046: import java.util.List;
047: import java.util.ArrayList;
048: import java.util.Collections;
049: import java.beans.PropertyChangeListener;
050: import java.beans.PropertyChangeSupport;
051: import java.net.URL;
052: import java.util.Iterator;
053: import java.util.StringTokenizer;
054: import org.netbeans.modules.j2ee.ejbjarproject.ui.customizer.EjbJarProjectProperties;
055: import org.netbeans.modules.java.api.common.SourceRoots;
056: import org.netbeans.spi.java.classpath.ClassPathImplementation;
057: import org.netbeans.spi.java.classpath.PathResourceImplementation;
058: import org.netbeans.spi.java.classpath.support.ClassPathSupport;
059: import org.netbeans.spi.project.support.ant.AntProjectHelper;
060: import org.openide.filesystems.FileChangeAdapter;
061: import org.openide.filesystems.FileChangeListener;
062: import org.openide.filesystems.FileEvent;
063: import org.openide.filesystems.FileObject;
064: import org.openide.filesystems.FileUtil;
065: import org.openide.util.Exceptions;
066: import org.openide.util.RequestProcessor;
067:
068: /**
069: * Implementation of a single classpath that is derived from one Ant property.
070: */
071: final class SourcePathImplementation implements
072: ClassPathImplementation, PropertyChangeListener {
073: private static final String DIR_GEN_BINDINGS = "generated/addons"; // NOI18N
074: private static RequestProcessor REQ_PROCESSOR = new RequestProcessor(); // No I18N
075:
076: private PropertyChangeSupport support = new PropertyChangeSupport(
077: this );
078: private List<PathResourceImplementation> resources;
079: private SourceRoots sourceRoots;
080: private AntProjectHelper projectHelper;
081: private FileChangeListener fcl = null;
082:
083: /**
084: * Construct the implementation.
085: * @param sourceRoots used to get the roots information and events
086: */
087: public SourcePathImplementation(SourceRoots sourceRoots,
088: AntProjectHelper projectHelper) {
089: assert sourceRoots != null;
090: this .sourceRoots = sourceRoots;
091: this .projectHelper = projectHelper;
092: this .sourceRoots.addPropertyChangeListener(this );
093: }
094:
095: private synchronized void createAddOnGenSrcRootsListener(
096: String buildDir, String[] paths) {
097: if (this .fcl == null) {
098: // Need to keep reference to fcl.
099: // See JavaDoc for org.openide.util.WeakListeners
100: FileObject prjFo = this .projectHelper.getProjectDirectory();
101: this .fcl = new AddOnGeneratedSourceRootListener(prjFo,
102: buildDir, paths);
103: ((AddOnGeneratedSourceRootListener) this .fcl)
104: .listenToProjRoot();
105: }
106: }
107:
108: private List<PathResourceImplementation> getAddOnGeneratedSrcRoots(
109: String buildDir, String[] paths) {
110: List<PathResourceImplementation> ret = new ArrayList<PathResourceImplementation>();
111:
112: File buidDirFile = projectHelper.resolveFile(buildDir);
113: for (String path : paths) {
114: File genAddOns = new File(buidDirFile, path);
115: if (genAddOns.exists() && genAddOns.isDirectory()) {
116: File[] subDirs = genAddOns.listFiles();
117: for (File subDir : subDirs) {
118: try {
119: URL url = subDir.toURI().toURL();
120: if (!subDir.exists()) {
121: assert !url.toExternalForm().endsWith("/"); //NOI18N
122: url = new URL(url.toExternalForm() + '/'); //NOI18N
123: }
124: ret.add(ClassPathSupport.createResource(url));
125: } catch (MalformedURLException ex) {
126: Exceptions.printStackTrace(ex);
127: }
128: }
129: }
130: }
131: return ret;
132: }
133:
134: private void invalidate() {
135: synchronized (this ) {
136: this .resources = null;
137: }
138: this .support.firePropertyChange(PROP_RESOURCES, null, null);
139: }
140:
141: public List<PathResourceImplementation> getResources() {
142: synchronized (this ) {
143: if (this .resources != null) {
144: return this .resources;
145: }
146: }
147: URL[] roots = this .sourceRoots.getRootURLs();
148: String buildDir = projectHelper.getStandardPropertyEvaluator()
149: .getProperty(EjbJarProjectProperties.BUILD_DIR);
150: synchronized (this ) {
151: if (this .resources == null) {
152: List<PathResourceImplementation> result = new ArrayList<PathResourceImplementation>(
153: roots.length);
154: for (int i = 0; i < roots.length; i++) {
155: PathResourceImplementation res = ClassPathSupport
156: .createResource(roots[i]);
157: result.add(res);
158: }
159: // adds build/generated/wsimport/client and build/generated/wsimport/service to resources to be available for code completion
160: if (projectHelper != null) {
161: try {
162: if (buildDir != null) {
163: // generated/wsimport/client
164: File f = new File(projectHelper
165: .resolveFile(buildDir),
166: "generated/wsimport/client"); //NOI18N
167: URL url = f.toURI().toURL();
168: if (!f.exists()) { //NOI18N
169: assert !url.toExternalForm().endsWith(
170: "/"); //NOI18N
171: url = new URL(
172: url.toExternalForm() + '/'); //NOI18N
173: }
174: result.add(ClassPathSupport
175: .createResource(url));
176: // generated/wsimport/service
177: f = new File(projectHelper
178: .resolveFile(buildDir),
179: "generated/wsimport/service"); //NOI18N
180: url = f.toURI().toURL();
181: if (!f.exists()) { //NOI18N
182: assert !url.toExternalForm().endsWith(
183: "/"); //NOI18N
184: url = new URL(
185: url.toExternalForm() + '/'); //NOI18N
186: }
187: result.add(ClassPathSupport
188: .createResource(url));
189:
190: // generated/addons/<subDirs>
191: result.addAll(getAddOnGeneratedSrcRoots(
192: buildDir,
193: new String[] { DIR_GEN_BINDINGS }));
194: // Listen for any new Source root creation.
195: createAddOnGenSrcRootsListener(buildDir,
196: new String[] { DIR_GEN_BINDINGS });
197: }
198: } catch (MalformedURLException ex) {
199: Exceptions.printStackTrace(ex);
200: }
201: }
202: this .resources = Collections.unmodifiableList(result);
203: }
204: }
205: return this .resources;
206: }
207:
208: public void addPropertyChangeListener(
209: PropertyChangeListener listener) {
210: support.addPropertyChangeListener(listener);
211: }
212:
213: public void removePropertyChangeListener(
214: PropertyChangeListener listener) {
215: support.removePropertyChangeListener(listener);
216: }
217:
218: public void propertyChange(PropertyChangeEvent evt) {
219: if (SourceRoots.PROP_ROOTS.equals(evt.getPropertyName())) {
220: invalidate();
221: }
222: }
223:
224: /**
225: * Thread/task to check newly created source root for each File/Folder create event.
226: **/
227: private static class SourceRootScannerTask implements Runnable {
228: SourcePathImplementation spi = null;
229: FileChangeListener fcl = null;
230: List<List<String>> paths = null;
231: FileObject parent = null;
232: FileObject child = null;
233: List<String> listenerAddedDirs = new ArrayList<String>();
234:
235: public SourceRootScannerTask(SourcePathImplementation s,
236: FileChangeListener origFcl, List<List<String>> pths,
237: FileObject parent, FileObject child) {
238: this .spi = s;
239: this .fcl = origFcl;
240: this .paths = pths;
241: this .parent = parent;
242: this .child = child;
243: }
244:
245: private void firePropertyChange() {
246: spi.invalidate();
247: }
248:
249: private void addListeners(List<String> path, int cIndx) {
250: int size = path.size();
251: FileObject currParent = this .parent;
252: FileObject curr = this .child;
253: String relDir = null;
254: FileChangeListener weakFcl = null;
255: for (int i = cIndx; i < size; i++) {
256: curr = currParent.getFileObject(path.get(i));
257: if ((curr != null) && (curr.isFolder())) {
258: relDir = FileUtil
259: .getRelativePath(this .parent, curr);
260: if (!this .listenerAddedDirs.contains(relDir)) {
261: this .listenerAddedDirs.add(relDir);
262: weakFcl = FileUtil.weakFileChangeListener(
263: this .fcl, curr);
264: curr.addFileChangeListener(weakFcl);
265: }
266:
267: if (i == (size - 1)) {
268: if (curr.getChildren().length > 0) {
269: firePropertyChange();
270: }
271: break;
272: }
273:
274: currParent = curr;
275: } else {
276: break;
277: }
278: }
279: }
280:
281: public void run() {
282: Iterator<List<String>> itr = paths.iterator();
283: List<String> path = null;
284: int cIndx = -1;
285: int pIndx = -1;
286: boolean lastElem = false;
287:
288: while (itr.hasNext()) {
289: path = itr.next();
290: cIndx = path.indexOf(child.getName());
291: pIndx = path.indexOf(parent.getName());
292:
293: lastElem = ((pIndx + 1) == path.size()) ? true : false;
294:
295: if (lastElem) {
296: if (cIndx == -1) {
297: firePropertyChange();
298: }
299: } else {
300: if ((cIndx != -1) && (pIndx == (cIndx - 1))) {
301: // Add listener and fire change event if leaf directory
302: // is created.
303: addListeners(path, cIndx);
304: }
305: }
306: }
307: }
308: }
309:
310: private class AddOnGeneratedSourceRootListener extends
311: FileChangeAdapter {
312: // Path is relative to project root, starting with project specific
313: // build directory.
314: private List<List<String>> paths = Collections
315: .synchronizedList(new ArrayList<List<String>>());
316: private FileObject projRoot;
317:
318: AddOnGeneratedSourceRootListener(FileObject pr, String bd,
319: String[] addOnPaths) {
320: this .projRoot = pr;
321: StringTokenizer stk = null;
322: List<String> pathElems = null;
323: for (String path : addOnPaths) {
324: stk = new StringTokenizer(path, "/"); // No I18N
325: pathElems = new ArrayList<String>();
326: pathElems.add(bd);
327: while (stk.hasMoreTokens()) {
328: pathElems.add(stk.nextToken());
329: }
330: this .paths.add(pathElems);
331: }
332: }
333:
334: /**
335: * Listen to all the folders from ProjectRoot, build upto any existing
336: * addons dirs.
337: **/
338: public synchronized void listenToProjRoot() {
339: List<String> dirsAdded = new ArrayList<String>();
340: String relativePath = null;
341: FileObject fo = this .projRoot;
342: FileChangeListener weakFcl = FileUtil
343: .weakFileChangeListener(this , fo);
344: fo.addFileChangeListener(weakFcl);
345: FileObject parent = null;
346: FileObject child = null;
347: for (List<String> path : paths) {
348: parent = fo;
349: for (String pathElem : path) {
350: child = parent.getFileObject(pathElem);
351: if (child != null) {
352: relativePath = FileUtil.getRelativePath(fo,
353: child);
354: if (!dirsAdded.contains(relativePath)) {
355: dirsAdded.add(relativePath);
356: weakFcl = FileUtil.weakFileChangeListener(
357: this , child);
358: child.addFileChangeListener(weakFcl);
359: parent = child;
360: }
361: } else {
362: // No need to check further down.
363: break;
364: }
365: }
366: }
367: }
368:
369: @Override
370: public void fileFolderCreated(FileEvent fe) {
371: synchronized (this ) {
372: SourceRootScannerTask task = new SourceRootScannerTask(
373: SourcePathImplementation.this , this ,
374: this .paths, (FileObject) fe.getSource(), fe
375: .getFile());
376: SourcePathImplementation.REQ_PROCESSOR.post(task);
377: }
378: }
379: }
380: }
|