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.visualweb.insync.java;
042:
043: import com.sun.source.tree.CompilationUnitTree;
044: import com.sun.source.tree.ExpressionTree;
045: import com.sun.source.tree.IdentifierTree;
046: import com.sun.source.tree.ImportTree;
047: import com.sun.source.tree.MemberSelectTree;
048: import com.sun.source.tree.Tree;
049: import com.sun.source.util.TreePath;
050: import java.io.IOException;
051: import java.io.OutputStream;
052: import java.io.PrintWriter;
053: import java.io.Writer;
054: import java.net.URLClassLoader;
055: import java.util.ArrayList;
056: import java.util.List;
057: import java.util.Locale;
058: import java.util.concurrent.Future;
059: import javax.lang.model.element.Element;
060: import javax.lang.model.element.ElementKind;
061: import javax.lang.model.element.PackageElement;
062:
063: import javax.swing.event.DocumentEvent;
064: import javax.tools.Diagnostic;
065: import org.netbeans.api.java.source.CompilationController;
066: import org.netbeans.api.java.source.CompilationInfo;
067: import org.netbeans.api.java.source.JavaSource;
068: import org.netbeans.api.java.source.SourceUtils;
069: import org.netbeans.api.java.source.Task;
070: import org.netbeans.api.java.source.WorkingCopy;
071:
072: import org.openide.filesystems.FileObject;
073: import org.netbeans.modules.visualweb.extension.openide.util.Trace;
074:
075: import org.netbeans.modules.visualweb.insync.ParserAnnotation;
076: import org.netbeans.modules.visualweb.insync.SourceUnit;
077: import org.netbeans.modules.visualweb.insync.UndoManager;
078: import org.netbeans.modules.visualweb.insync.Unit.State;
079: import org.netbeans.modules.visualweb.insync.Util;
080:
081: /**
082: * <p>A SourceUnit that manages a Java source file.</p>
083: * @author Carl Quinn
084: * @version 1.0
085: */
086: public class JavaUnit extends SourceUnit {
087:
088: URLClassLoader classLoader;
089: private boolean markSourceDirty = true;
090:
091: private enum ImportStatus {
092: needed, not_needed, exists, not_allowed
093: }
094:
095: ParserAnnotation[] errors = ParserAnnotation.EMPTY_ARRAY;
096:
097: //--------------------------------------------------------------------------------- Construction
098:
099: /**
100: * Construct a JavaUnit from an existing source Document
101: */
102: public JavaUnit(FileObject fobj, URLClassLoader cl,
103: UndoManager undoManager) {
104: super (fobj, undoManager);
105: //Trace.enableTraceCategory("insync.java");
106: }
107:
108: /*
109: * Clean up all of our big resources.
110: * @see org.netbeans.modules.visualweb.insync.Unit#destroy()
111: */
112: public void destroy() {
113: classLoader = null;
114: super .destroy();
115: }
116:
117: //---------------------------------------------------------------------------------------- Input
118:
119: protected void read(char[] cbuf, int len) {
120:
121: }
122:
123: /**
124: * Read the errors in source
125: */
126: protected void readErrors() {
127: ReadTaskWrapper.execute(new ReadTaskWrapper.Read() {
128: public Object run(CompilationInfo cinfo) {
129: List<ParserAnnotation> parserAnnotationsList = new ArrayList<ParserAnnotation>();
130: for (Diagnostic diagnostic : cinfo.getDiagnostics()) {
131: if (diagnostic.getKind() == Diagnostic.Kind.ERROR) {
132: ParserAnnotation annotation = new ParserAnnotation(
133: diagnostic.getMessage(Locale
134: .getDefault()), fobj,
135: (int) diagnostic.getLineNumber(),
136: (int) diagnostic.getColumnNumber());
137: parserAnnotationsList.add(annotation);
138: }
139: }
140: errors = parserAnnotationsList
141: .toArray(ParserAnnotation.EMPTY_ARRAY);
142: return null;
143: }
144: }, fobj, false);
145:
146: //Java source may not be parsed successfully if the project has dependency on libraries
147: //or source roots that are not yet scanned. Therefore wait for the scan to complete and
148: //check for errors again
149: if (errors.length > 0 && SourceUtils.isScanInProgress()) {
150: try {
151: Future future = JavaSource.forFileObject(fobj)
152: .runWhenScanFinished(
153: new Task<CompilationController>() {
154: public void run(
155: CompilationController cc)
156: throws Exception {
157: }
158: }, true);
159: //Wait till the scan is done
160: future.get();
161: //Try reading errors again
162: readErrors();
163: } catch (Exception e) {
164: //Ignore exception and continue to show the errors to user
165: }
166: }
167: }
168:
169: /**
170: * @param out
171: * @throws java.io.IOException
172: */
173: public void writeTo(OutputStream out) throws java.io.IOException {
174: }
175:
176: /**
177: * Implicit read. Read the document supplied during construction into this model.
178: *
179: * @return whether or not the read affected the model.
180: */
181: public boolean sync() {
182: //Grab the current document from our underlying editor.
183: //The property change listener from editor cookie (see SourceUnit)
184: //will add UndoableEditListener to the document.
185:
186: //XXX We need to revisit this as the document is unnecessarily grabbed
187: //It should be probably opened in the JavaClassAdapter before write transaction
188: //to restrict being opened only during flush - Winston
189:
190: Util.retrieveDocument(fobj, true);
191:
192: // make sure it is necessary & ok to read.
193: State state = getState();
194: assert Trace.trace("insync", "SU.sync of " + getName()
195: + " state:" + state);
196: if (state == State.CLEAN)
197: return false;
198: if (state == State.MODELDIRTY) {
199: assert Trace
200: .trace("insync",
201: "SU.sync attempt to read source into a dirty model");
202: //Trace.printStackTrace();
203: return false;
204: }
205:
206: readErrors();
207:
208: if (errors.length > 0) {
209: setBusted();
210: } else {
211: setClean();
212: }
213:
214: return true;
215: }
216:
217: //------------------------------------------------------------------------------------ Accessors
218:
219: /*
220: * Returns the public class in the file JavaUnit represents
221: */
222: public JavaClass getJavaClass() {
223: return JavaClass.getJavaClass(fobj);
224: }
225:
226: private String getPackageName(CompilationInfo cinfo) {
227: Element e = cinfo.getTrees().getElement(
228: new TreePath(cinfo.getCompilationUnit()));
229: if (e != null && e.getKind() == ElementKind.PACKAGE) {
230: return ((PackageElement) e).getQualifiedName().toString();
231: }
232: return null;
233: }
234:
235: /*
236: * @see org.netbeans.modules.visualweb.insync.Unit#getErrors()
237: */
238: public ParserAnnotation[] getErrors() {
239: if (errors == null)
240: readErrors();
241: return errors;
242: }
243:
244: //------------------------------------------------------------------------------------- Mutators
245:
246: /**
247: * Ensure that a given import is in the list, if not try to add it
248: * @param
249: * @return true if the import was added if required or if the import is not
250: * required to be added
251: */
252: public boolean ensureImport(final String fqn) {
253: ImportStatus result = (ImportStatus) WriteTaskWrapper.execute(
254: new WriteTaskWrapper.Write() {
255: public Object run(WorkingCopy wc) {
256: ImportStatus status = checkImportStatus(wc, fqn);
257: if (status.equals(ImportStatus.needed)) {
258: addImport(wc, fqn);
259: }
260: return status;
261: }
262: }, getFileObject());
263: //Check if the import is successfully added
264: if (result.equals(ImportStatus.needed)) {
265: return isImported(fqn);
266: } else if (result.equals(ImportStatus.not_allowed)) {
267: return false;
268: } else {
269: return true;
270: }
271: }
272:
273: /**
274: * Add a new import
275: *
276: */
277: private void addImport(WorkingCopy wc, String fqn) {
278: ImportTree imprt = TreeMakerUtils.createImport(wc, fqn);
279: CompilationUnitTree cunit = wc.getTreeMaker()
280: .addCompUnitImport(wc.getCompilationUnit(), imprt);
281: wc.rewrite(wc.getCompilationUnit(), cunit);
282: }
283:
284: /**
285: * @return true if the class has been imported
286: *
287: */
288: private boolean isImported(final String fqn) {
289: return (Boolean) ReadTaskWrapper.execute(
290: new ReadTaskWrapper.Read() {
291: public Object run(CompilationInfo cinfo) {
292: for (ImportTree importTree : cinfo
293: .getCompilationUnit().getImports()) {
294: if (importTree.getQualifiedIdentifier()
295: .getKind() == Tree.Kind.MEMBER_SELECT) {
296: MemberSelectTree memSelectTree = (MemberSelectTree) importTree
297: .getQualifiedIdentifier();
298: if (getFQN(cinfo, memSelectTree)
299: .equals(fqn)) {
300: return true;
301: }
302: }
303: }
304: return false;
305: }
306: }, getFileObject());
307: }
308:
309: /**
310: * @return computes the fqn for ExpressionTree taking multi byte character into account
311: * toString() on trees do not support multi-byte characters, therefore this method is added
312: */
313: private String getFQN(CompilationInfo cinfo, ExpressionTree tree) {
314: if (tree.getKind() == Tree.Kind.MEMBER_SELECT) {
315: MemberSelectTree memSelectTree = (MemberSelectTree) tree;
316: return getFQN(cinfo, memSelectTree.getExpression()) + "."
317: + memSelectTree.getIdentifier().toString();
318: } else if (tree.getKind() == Tree.Kind.IDENTIFIER) {
319: return ((IdentifierTree) tree).getName().toString();
320: }
321: return tree.toString();
322: }
323:
324: /**
325: * @return needed if the import is required for the class
326: * not_needed if the import is not required
327: * exists if the import exists
328: * not_allowed if the import is not allowed
329: * Checks if the class is in same package, or if the class is already imported,
330: * or if the wild card imports exists for class's package and if a different
331: * class by same name but in different package has been imported
332: */
333: private ImportStatus checkImportStatus(CompilationInfo cinfo,
334: String fqn) {
335: int index = fqn.lastIndexOf('.');
336: String pkgName = null;
337: String className = null;
338: if (index != -1) {
339: pkgName = fqn.substring(0, index);
340: className = fqn.substring(index + 1);
341: }
342:
343: //check if they belong to same package
344: String currentPkgName = getPackageName(cinfo);
345: if (pkgName == null || pkgName.equals(currentPkgName)) {
346: return ImportStatus.not_needed;
347: }
348:
349: //check if the class by same name exists in the package
350: if (cinfo.getElements().getTypeElement(
351: currentPkgName + "." + className) != null) {
352: return ImportStatus.not_allowed;
353: }
354:
355: for (ImportTree importTree : cinfo.getCompilationUnit()
356: .getImports()) {
357: if (importTree.getQualifiedIdentifier().getKind() == Tree.Kind.MEMBER_SELECT) {
358: MemberSelectTree memSelectTree = (MemberSelectTree) importTree
359: .getQualifiedIdentifier();
360: String importClassName = memSelectTree.getIdentifier()
361: .toString();
362: //FQN match
363: if (getFQN(cinfo, memSelectTree).equals(fqn)) {
364: return ImportStatus.exists;
365: }
366: //Check for wild card imports
367: if (importClassName.equals("*")) {
368: if (getFQN(cinfo, memSelectTree.getExpression())
369: .equals(pkgName)) {
370: return ImportStatus.exists;
371: }
372: } else {
373: //Check if the import is not allowed because of name clash
374: if (importClassName.equals(className)) {
375: return ImportStatus.not_allowed;
376: }
377: }
378: }
379: }
380: return ImportStatus.needed;
381: }
382:
383: /**
384: *
385: */
386: public JavaClass addClass(String name) {
387: return null;
388: }
389:
390: /**
391: *
392: */
393: public void removeImport(String ident) {
394: }
395:
396: /**
397: *
398: */
399: public void removeClass(JavaClass cls) {
400: }
401:
402: //--------------------------------------------------------------------------------------
403: public boolean flush() {
404: return true;
405: }
406:
407: public void writeTo(Writer w) throws IOException {
408: }
409:
410: public void dumpTo(PrintWriter w) {
411: }
412:
413: protected synchronized void firstWriteLock() {
414: markSourceDirty = false;
415: }
416:
417: protected synchronized void lastWriteUnlock() {
418: markSourceDirty = true;
419: }
420:
421: //----------------------------------------------------------------------------- DocumentListener
422:
423: /*
424: * @see javax.swing.event.DocumentListener#insertUpdate(javax.swing.event.DocumentEvent)
425: */
426: public void insertUpdate(DocumentEvent e) {
427: assert Trace.trace("insync-listener", "SU.insertUpdate");
428: undoManager.notifyBufferEdited(this );
429: if (markSourceDirty) {
430: setSourceDirty();
431: }
432: }
433:
434: /*
435: * @see javax.swing.event.DocumentListener#removeUpdate(javax.swing.event.DocumentEvent)
436: */
437: public void removeUpdate(DocumentEvent e) {
438: assert Trace.trace("insync-listener", "SU.removeUpdate");
439: undoManager.notifyBufferEdited(this);
440: if (markSourceDirty) {
441: setSourceDirty();
442: }
443: }
444:
445: }
|