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: package org.netbeans.modules.refactoring.api;
042:
043: import java.util.ArrayList;
044: import java.util.Collection;
045: import java.util.HashSet;
046: import java.util.Iterator;
047: import java.util.List;
048: import org.netbeans.api.queries.SharabilityQuery;
049: import org.netbeans.modules.refactoring.api.impl.APIAccessor;
050: import org.netbeans.modules.refactoring.api.impl.ProgressSupport;
051: import org.netbeans.modules.refactoring.api.impl.SPIAccessor;
052: import org.netbeans.modules.refactoring.spi.GuardedBlockHandler;
053: import org.netbeans.modules.refactoring.spi.GuardedBlockHandlerFactory;
054: import org.netbeans.modules.refactoring.spi.ProgressProvider;
055: import org.netbeans.modules.refactoring.spi.ReadOnlyFilesHandler;
056: import org.netbeans.modules.refactoring.spi.RefactoringPlugin;
057: import org.netbeans.modules.refactoring.spi.RefactoringPluginFactory;
058: import org.netbeans.modules.refactoring.spi.impl.Util;
059: import org.openide.ErrorManager;
060: import org.openide.filesystems.FileObject;
061: import org.openide.filesystems.FileUtil;
062: import org.openide.loaders.DataObject;
063: import org.openide.loaders.DataObjectNotFoundException;
064: import org.openide.modules.ModuleInfo;
065: import org.openide.util.Lookup;
066: import org.netbeans.modules.refactoring.spi.RefactoringElementsBag;
067: import org.netbeans.modules.refactoring.spi.impl.RefactoringPanel;
068: import org.openide.util.Exceptions;
069: import org.openide.util.NbBundle;
070: import org.openide.util.lookup.InstanceContent;
071:
072: /**
073: * Abstract superclass for particular refactorings.
074: * Methods should be typically called in following order:
075: * <ul>
076: * <li>preCheck
077: * <li>setParameter1(..), setParameter2(..) (this methods will typically be added by the subclass
078: * to allow parametrization of the implemented refactoring)
079: * <li>fastCheckParameters() (performs only fast check - useful for online error checking)
080: * <li>checkParameters() (full check of parameters)
081: * <li>prepare() (collects usages)
082: * </ul>
083: * @see RefactoringSession
084: * @author Martin Matula, Jan Becicka
085: */
086: public abstract class AbstractRefactoring {
087:
088: static {
089: APIAccessor.DEFAULT = new AccessorImpl();
090: }
091:
092: /**
093: * Initial state
094: */
095: public static final int INIT = 0;
096: /** Pre-check state. */
097: public static final int PRE_CHECK = 1;
098: /** Parameters check state. */
099: public static final int PARAMETERS_CHECK = 2;
100: /** Prepare state. */
101: public static final int PREPARE = 3;
102:
103: private int currentState = INIT;
104:
105: private static final int PLUGIN_STEPS = 30;
106:
107: private ArrayList plugins;
108:
109: ArrayList pluginsWithProgress;
110:
111: private ArrayList gbHandlers;
112:
113: private ProgressListener progressListener = new ProgressL();
114:
115: private ProgressSupport progressSupport;
116:
117: Lookup refactoringSource;
118:
119: protected AbstractRefactoring(Lookup refactoringSource) {
120: this .refactoringSource = refactoringSource;
121: }
122:
123: private Collection getPlugins() {
124: if (plugins == null) {
125: plugins = new ArrayList();
126: // get plugins from the lookup
127: Lookup.Result result = Lookup.getDefault()
128: .lookup(
129: new Lookup.Template(
130: RefactoringPluginFactory.class));
131: for (Iterator it = result.allInstances().iterator(); it
132: .hasNext();) {
133: RefactoringPluginFactory factory = (RefactoringPluginFactory) it
134: .next();
135: RefactoringPlugin plugin = factory.createInstance(this );
136: if (plugin != null) {
137: RefactoringPlugin callerPlugin = getContext()
138: .lookup(RefactoringPlugin.class);
139: AbstractRefactoring caller = getContext().lookup(
140: AbstractRefactoring.class);
141: if (caller == null
142: || factory.getClass().getClassLoader()
143: .equals(
144: callerPlugin.getClass()
145: .getClassLoader())
146: || factory.createInstance(caller) == null) {
147: //caller is internal non-api field. Plugin is always added for API calls.
148: //For non-api internal calls:
149: // Plugins from different modules are ignored,
150: // if factory for the caller return non-null plugin.
151: // This quite hacky method is used for SafeDeleteRefactoring:
152: // SafeDeleteRefactorinPlugin uses WhereUsedQuery internally.
153: // If some module implements both plugins (SafeDelete and WhereUsed),
154: // WhereUsedRefactoringPlugin should be ignored, because whole process will be handled by
155: // SafeDeleteRefactoringPlugin.
156: // #65980
157: plugins.add(plugin);
158: }
159: }
160: }
161: }
162: return plugins;
163: }
164:
165: Collection getGBHandlers() {
166: if (gbHandlers == null) {
167: gbHandlers = new ArrayList();
168: // get plugins from the lookup
169: Lookup.Result result = Lookup.getDefault().lookup(
170: new Lookup.Template(
171: GuardedBlockHandlerFactory.class));
172: for (Iterator it = result.allInstances().iterator(); it
173: .hasNext();) {
174: GuardedBlockHandler handler = ((GuardedBlockHandlerFactory) it
175: .next()).createInstance(this );
176: if (handler != null)
177: gbHandlers.add(handler);
178: }
179: }
180: return gbHandlers;
181: }
182:
183: /** Perform checks to ensure that the preconditions are met for the implemented
184: * refactoring.
185: * @return Chain of problems encountered or <code>null</code> if no problems
186: * were found.
187: */
188: public final Problem preCheck() {
189: // //workaround for #68803
190: // if (!(this instanceof WhereUsedQuery)) {
191: // if (progressSupport != null)
192: // progressSupport.fireProgressListenerStart(this, ProgressEvent.START, -1);
193: // setCP();
194: // if (progressSupport != null)
195: // progressSupport.fireProgressListenerStop(this);
196: // }
197: currentState = PRE_CHECK;
198: return pluginsPreCheck(null);
199: }
200:
201: /** Collects and returns a set of refactoring elements - objects that
202: * will be affected by the refactoring.
203: * @param session RefactoringSession that the operation will use to return
204: * instances of {@link org.netbeans.modules.refactoring.api.RefactoringElement} class representing objects that
205: * will be affected by the refactoring.
206: * @return Chain of problems encountered or <code>null</code> in no problems
207: * were found.
208: */
209: public final Problem prepare(RefactoringSession session) {
210: Problem p = null;
211: boolean checkCalled = false;
212: if (currentState < PARAMETERS_CHECK) {
213: p = checkParameters();
214: checkCalled = true;
215: }
216: if (p != null && p.isFatal())
217: return p;
218: return pluginsPrepare(checkCalled ? p : null, session);
219: }
220:
221: /**
222: * Checks if this refactoring has correctly set all parameters.
223: * @return Returns instancef Problem or null
224: */
225: public final Problem checkParameters() {
226: // //workaround for #68803
227: // if (this instanceof WhereUsedQuery) {
228: // if (progressSupport != null)
229: // progressSupport.fireProgressListenerStart(this, ProgressEvent.START, -1);
230: // setCP();
231: // if (progressSupport != null)
232: // progressSupport.fireProgressListenerStop(this);
233: // } else {
234: // setCP();
235: // }
236: Problem p = fastCheckParameters();
237: if (p != null && p.isFatal())
238: return p;
239: currentState = PARAMETERS_CHECK;
240: return pluginsCheckParams(p);
241: }
242:
243: /**
244: * This method checks parameters. Its implementation is fast and allows on-line checking of errors.
245: * If you want complete check of parameters, use #checkParameters()
246: * @return Returns instance of Problem or null
247: */
248: public final Problem fastCheckParameters() {
249: // Do not set classpath - use default merged class path
250: // #57558
251: // setCP();
252: Problem p = null;
253: if (currentState < PRE_CHECK) {
254: p = preCheck();
255: }
256: if (p != null && p.isFatal())
257: return p;
258: return pluginsFastCheckParams(p);
259: }
260:
261: /** Registers ProgressListener to receive events.
262: * @param listener The listener to register.
263: *
264: */
265: public final synchronized void addProgressListener(
266: ProgressListener listener) {
267: if (progressSupport == null) {
268: progressSupport = new ProgressSupport();
269: }
270: progressSupport.addProgressListener(listener);
271:
272: if (pluginsWithProgress == null) {
273: pluginsWithProgress = new ArrayList();
274: Iterator pIt = getPlugins().iterator();
275: while (pIt.hasNext()) {
276: RefactoringPlugin plugin = (RefactoringPlugin) pIt
277: .next();
278: if (plugin instanceof ProgressProvider) {
279: ((ProgressProvider) plugin)
280: .addProgressListener(progressListener);
281: pluginsWithProgress.add(plugin);
282: }
283: }
284: }
285: }
286:
287: /** Removes ProgressListener from the list of listeners.
288: * @param listener The listener to remove.
289: *
290: */
291: public final synchronized void removeProgressListener(
292: ProgressListener listener) {
293: if (progressSupport != null) {
294: progressSupport.removeProgressListener(listener);
295: }
296:
297: if (pluginsWithProgress != null) {
298: Iterator pIt = pluginsWithProgress.iterator();
299:
300: while (pIt.hasNext()) {
301: ProgressProvider plugin = (ProgressProvider) pIt.next();
302: plugin.removeProgressListener(progressListener);
303: }
304: pluginsWithProgress.clear();
305: pluginsWithProgress = null;
306: }
307: }
308:
309: /**
310: * getter for refactoring Context
311: * @see Context
312: * @return context in which the refactoring was invoked.
313: */
314: public final Context getContext() {
315: if (this .scope == null) {
316: this .scope = new Context(new InstanceContent());
317: }
318: return this .scope;
319: }
320:
321: /**
322: * Object being refactored
323: * @return
324: */
325: public final Lookup getRefactoringSource() {
326: return refactoringSource;
327: }
328:
329: private Context scope;
330:
331: private volatile boolean cancel;
332:
333: /**
334: * Asynchronous request to cancel ongoing long-term request (such as preCheck(), checkParameters() or prepare())
335: */
336: public final void cancelRequest() {
337: cancel = true;
338: Iterator pIt = getPlugins().iterator();
339:
340: while (pIt.hasNext()) {
341: RefactoringPlugin plugin = (RefactoringPlugin) pIt.next();
342: plugin.cancelRequest();
343: }
344: }
345:
346: private Problem pluginsPreCheck(Problem problem) {
347: Iterator pIt = getPlugins().iterator();
348:
349: while (pIt.hasNext()) {
350: if (cancel)
351: return null;
352: RefactoringPlugin plugin = (RefactoringPlugin) pIt.next();
353:
354: try {
355: problem = chainProblems(plugin.preCheck(), problem);
356: } catch (Throwable t) {
357: problem = createProblemAndLog(problem, t, plugin
358: .getClass());
359: }
360: if (problem != null && problem.isFatal())
361: return problem;
362: }
363: return problem;
364: }
365:
366: private String getModuleName(Class c) {
367: for (ModuleInfo info : Lookup.getDefault().lookupAll(
368: ModuleInfo.class)) {
369: if (info.owns(c)) {
370: return info.getDisplayName();
371: }
372: }
373: return "Unknown";//NOI18N
374: }
375:
376: private String createMessage(Class c, Throwable t) {
377: return NbBundle
378: .getMessage(RefactoringPanel.class,
379: "ERR_ExceptionInModule", getModuleName(c), t
380: .toString());
381: }
382:
383: private Problem createProblemAndLog(Problem p, Throwable t,
384: Class source) {
385: Throwable cause = t.getCause();
386: Problem newProblem;
387: if (cause != null
388: && (cause
389: .getClass()
390: .getName()
391: .equals(
392: "org.netbeans.api.java.source.JavaSource$InsufficientMemoryException") || //NOI18N
393: (cause.getCause() != null)
394: && cause
395: .getCause()
396: .getClass()
397: .getName()
398: .equals(
399: "org.netbeans.api.java.source.JavaSource$InsufficientMemoryException"))) { //NOI18N
400: newProblem = new Problem(true, NbBundle.getMessage(
401: Util.class, "ERR_OutOfMemory"));
402: } else {
403: newProblem = new Problem(false, createMessage(source, t));
404: }
405: Exceptions.printStackTrace(t);
406: return chainProblems(newProblem, p);
407: }
408:
409: private Problem pluginsPrepare(Problem problem,
410: RefactoringSession session) {
411: RefactoringElementsBag elements = session.getElementsBag();
412: Iterator pIt = getPlugins().iterator();
413:
414: while (pIt.hasNext()) {
415: if (cancel)
416: return null;
417: RefactoringPlugin plugin = (RefactoringPlugin) pIt.next();
418:
419: try {
420: problem = chainProblems(plugin.prepare(elements),
421: problem);
422: } catch (Throwable t) {
423: problem = createProblemAndLog(problem, t, plugin
424: .getClass());
425: }
426: if (problem != null && problem.isFatal())
427: return problem;
428: }
429:
430: //TODO:
431: //following condition "!(this instanceof WhereUsedQuery)" is hotfix of #65785
432: //correct solution would probably be this condition: "!isQuery()"
433: //unfortunately isQuery() is not in AbstractRefactoring class, but in RefactoringIU
434: //we should consider moving this method to AbstractRefactoring class in future release
435: if (!(this instanceof WhereUsedQuery)) {
436: ReadOnlyFilesHandler handler = getROHandler();
437: if (handler != null) {
438: Collection files = SPIAccessor.DEFAULT
439: .getReadOnlyFiles(elements);
440: Collection allFiles = new HashSet();
441: for (Iterator i = files.iterator(); i.hasNext();) {
442: FileObject f = (FileObject) i.next();
443: DataObject dob;
444: try {
445: dob = DataObject.find(f);
446: for (Iterator j = dob.files().iterator(); j
447: .hasNext();) {
448: FileObject file = (FileObject) j.next();
449: if (SharabilityQuery
450: .getSharability(FileUtil
451: .toFile(file)) == SharabilityQuery.SHARABLE) {
452: allFiles.add(file);
453: }
454: }
455: } catch (DataObjectNotFoundException e) {
456: allFiles.add(f);
457: }
458: }
459: problem = chainProblems(handler.createProblem(session,
460: allFiles), problem);
461: }
462: }
463:
464: return problem;
465: }
466:
467: private ReadOnlyFilesHandler getROHandler() {
468: Lookup.Result result = Lookup.getDefault().lookup(
469: new Lookup.Template(ReadOnlyFilesHandler.class));
470: List handlers = (List) result.allInstances();
471: if (handlers.size() == 0) {
472: return null;
473: }
474: if (handlers.size() > 1) {
475: ErrorManager
476: .getDefault()
477: .log(
478: ErrorManager.WARNING,
479: "Multiple instances of ReadOnlyFilesHandler found in Lookup; only using first one: "
480: + handlers); //NOI18N
481: }
482: return (ReadOnlyFilesHandler) handlers.get(0);
483: }
484:
485: private Problem pluginsCheckParams(Problem problem) {
486: Iterator pIt = getPlugins().iterator();
487:
488: while (pIt.hasNext()) {
489: if (cancel)
490: return null;
491:
492: RefactoringPlugin plugin = (RefactoringPlugin) pIt.next();
493:
494: try {
495: problem = chainProblems(plugin.checkParameters(),
496: problem);
497: } catch (Throwable t) {
498: problem = createProblemAndLog(problem, t, plugin
499: .getClass());
500: }
501: if (problem != null && problem.isFatal())
502: return problem;
503: }
504: return problem;
505: }
506:
507: private Problem pluginsFastCheckParams(Problem problem) {
508: Iterator pIt = getPlugins().iterator();
509:
510: while (pIt.hasNext()) {
511: if (cancel)
512: return null;
513:
514: RefactoringPlugin plugin = (RefactoringPlugin) pIt.next();
515:
516: try {
517: problem = chainProblems(plugin.fastCheckParameters(),
518: problem);
519: } catch (Throwable t) {
520: problem = createProblemAndLog(problem, t, plugin
521: .getClass());
522: }
523: if (problem != null && problem.isFatal())
524: return problem;
525: }
526: return problem;
527: }
528:
529: static Problem chainProblems(Problem p, Problem p1) {
530: Problem problem;
531:
532: if (p == null)
533: return p1;
534: if (p1 == null)
535: return p;
536: problem = p;
537: while (problem.getNext() != null) {
538: problem = problem.getNext();
539: }
540: problem.setNext(p1);
541: return p;
542: }
543:
544: private class ProgressL implements ProgressListener {
545:
546: private float progressStep;
547: private float current;
548:
549: public void start(ProgressEvent event) {
550: progressStep = (float) PLUGIN_STEPS / event.getCount();
551:
552: if (pluginsWithProgress.indexOf(event.getSource()) == 0) {
553: //first plugin
554: //let's start
555: current = 0;
556: if (event.getCount() == -1) {
557: fireProgressListenerStart(event.getOperationType(),
558: -1);
559: } else {
560: fireProgressListenerStart(event.getOperationType(),
561: PLUGIN_STEPS * pluginsWithProgress.size());
562: }
563: } else {
564: current = pluginsWithProgress
565: .indexOf(event.getSource())
566: * PLUGIN_STEPS;
567: fireProgressListenerStep((int) current);
568: }
569: }
570:
571: public void step(ProgressEvent event) {
572: current = current + progressStep;
573: fireProgressListenerStep((int) current);
574: }
575:
576: public void stop(ProgressEvent event) {
577: if (event.getSource() == pluginsWithProgress
578: .get(pluginsWithProgress.size() - 1)) {
579: //last plugin
580: //let's stop
581: fireProgressListenerStop();
582: }
583: }
584:
585: /** Notifies all registered listeners about the event.
586: *
587: * @param type Type of operation that is starting.
588: * @param count Number of steps the operation consists of.
589: *
590: */
591: private void fireProgressListenerStart(int type, int count) {
592: if (progressSupport != null)
593: progressSupport.fireProgressListenerStart(this , type,
594: count);
595: }
596:
597: /** Notifies all registered listeners about the event.
598: */
599: private void fireProgressListenerStep() {
600: if (progressSupport != null)
601: progressSupport.fireProgressListenerStep(this );
602: }
603:
604: /**
605: * Notifies all registered listeners about the event.
606: * @param count
607: */
608: private void fireProgressListenerStep(int count) {
609: if (progressSupport != null)
610: progressSupport.fireProgressListenerStep(this , count);
611: }
612:
613: /** Notifies all registered listeners about the event.
614: */
615: private void fireProgressListenerStop() {
616: if (progressSupport != null)
617: progressSupport.fireProgressListenerStop(this);
618: }
619: }
620: }
|