Fixing some bugs and making the plug-in compatible with Java 1.4
[phpeclipse.git] / net.sourceforge.phpeclipse.phpmanual / src / net / sourceforge / phpeclipse / phpmanual / views / PHPManualView.java
1 package net.sourceforge.phpeclipse.phpmanual.views;
2
3 import java.io.BufferedReader;
4 import java.io.FileNotFoundException;
5 import java.io.FileReader;
6 import java.io.IOException;
7 import java.io.InputStream;
8 import java.net.URL;
9 import java.util.ArrayList;
10 import java.util.regex.Matcher;
11 import java.util.zip.ZipEntry;
12 import java.util.zip.ZipFile;
13
14 import net.sourceforge.phpdt.internal.ui.text.JavaWordFinder;
15 import net.sourceforge.phpdt.internal.ui.viewsupport.ISelectionListenerWithAST;
16 import net.sourceforge.phpdt.internal.ui.viewsupport.SelectionListenerWithASTManager;
17 import net.sourceforge.phpdt.phphelp.PHPHelpPlugin;
18 import net.sourceforge.phpeclipse.PHPeclipsePlugin;
19 import net.sourceforge.phpeclipse.phpeditor.PHPEditor;
20 import net.sourceforge.phpeclipse.phpmanual.PHPManualUIPlugin;
21
22 import org.eclipse.core.runtime.Path;
23 import org.eclipse.core.runtime.Platform;
24 import org.eclipse.jface.text.IDocument;
25 import org.eclipse.jface.text.IRegion;
26 import org.eclipse.jface.text.ITextSelection;
27 import org.eclipse.jface.viewers.ISelection;
28 import org.eclipse.swt.SWT;
29 import org.eclipse.swt.widgets.Composite;
30 import org.eclipse.swt.widgets.Display;
31 import org.eclipse.swt.browser.Browser;
32 import org.eclipse.ui.IEditorPart;
33 import org.eclipse.ui.INullSelectionListener;
34 import org.eclipse.ui.ISelectionListener;
35 import org.eclipse.ui.IWorkbenchPart;
36 import org.eclipse.ui.part.ViewPart;
37 import org.htmlparser.Node;
38 import org.htmlparser.Parser;
39 import org.htmlparser.tags.Div;
40 import org.htmlparser.util.ParserException;
41 import org.htmlparser.visitors.TagFindingVisitor;
42 import org.osgi.framework.Bundle;
43
44 /**
45  * This ViewPart is the implementation of the idea of having the 
46  * PHP Manual easily accessible while coding. It shows the
47  * under-cursor function's reference inside a browser.
48  * <p>
49  * The view listens to selection changes both in the (1)workbench, to
50  * know when the user changes between the instances of the PHPEditor
51  * or when a new instance is created; and in the (2)PHPEditor, to know
52  * when the user changes the cursor position. This explains the need
53  * to implement both ISelectionListener and ISelectionListenerWithAST.
54  * <p>
55  * Up to now, the ViewPart show reference pages from HTML stored in the
56  * doc.zip file from the net.sourceforge.phpeclipse.phphelp plugin. It
57  * also depends on net.sourceforge.phpeclipse.phpmanual.htmlparser to
58  * parse these HTML files.
59  * <p>
60  * @author scorphus
61  */
62 public class PHPManualView extends ViewPart implements INullSelectionListener, ISelectionListenerWithAST {
63
64         /**
65          * The ViewPart's browser
66          */
67         private Browser browser;
68
69         /**
70          * A reference to store last active editor to know when we've
71          * got a new instance of the PHPEditor
72          */
73         private PHPEditor lastEditor;
74
75         /**
76          * String that stores the last selected word
77          */
78         private String lastOccurrence = null;
79
80         /**
81          * The path to the doc.zip file containing the PHP Manual
82          * in HTML format
83          */
84         private final Path docPath = new Path("doc.zip"); 
85
86         /**
87          * The constructor.
88          */
89         public PHPManualView() {
90         }
91
92         /**
93          * This method initializes the ViewPart. It instantiates components
94          * and add listeners
95          * 
96          * @param parent The parent control
97          */
98         public void createPartControl(Composite parent) {
99                 browser = new Browser(parent, SWT.NONE);
100                 parent.pack();
101                 if ((lastEditor = getJavaEditor()) != null) {
102                         SelectionListenerWithASTManager.getDefault().addListener(lastEditor, this);
103                 }
104                 getSite().getWorkbenchWindow().getSelectionService()
105                                 .addPostSelectionListener(PHPeclipsePlugin.EDITOR_ID, this);
106         }
107
108         /**
109          * Cleanup to remove the selection listener
110          */
111         public void dispose() {
112                 getSite().getWorkbenchWindow().getSelectionService()
113                                 .removePostSelectionListener(PHPeclipsePlugin.EDITOR_ID, this);
114         }
115
116         /**
117          * Passing the focus request to the viewer's control.
118          */
119         public void setFocus() {
120                 browser.setFocus();
121         }
122
123         /**
124          * Treats selection changes from the PHPEditor
125          */
126         public void selectionChanged(IEditorPart part, ITextSelection selection) {
127                 IDocument document = ((PHPEditor)part).getViewer().getDocument();
128                 int offset = selection.getOffset();
129                 IRegion iRegion = JavaWordFinder.findWord(document, offset);
130                 if (document != null && iRegion != null) {
131                         try {
132                                 final String wordStr = document.get(iRegion.getOffset(),
133                                                 iRegion.getLength());
134                                 if (!wordStr.equalsIgnoreCase(lastOccurrence)) {
135                                         showReference(wordStr);                         
136                                         lastOccurrence = wordStr;
137                                 }
138                         } catch (Exception e) {
139                                 e.printStackTrace();
140                         }
141                 }
142         }
143
144         /**
145          * Treats selection changes from the workbench. When part is new
146          * instance of PHPEditor it gets a listener attached
147          */
148         public void selectionChanged(IWorkbenchPart part, ISelection selection) {
149                 if (part != null && !((PHPEditor)part).equals(lastEditor)) {
150                         SelectionListenerWithASTManager.getDefault().addListener((PHPEditor)part, this);
151                         lastEditor = (PHPEditor)part;
152                 } else {
153                         System.out.println(part);
154                 }
155         }
156
157         /**
158          * Updates the browser with the reference page for a given function
159          * 
160          * @param funcName Function name
161          */
162         private void showReference(final String funcName) {
163                 System.out.println("Show reference for " + funcName);
164                 new Thread(new Runnable() {
165                         public void run() {
166                                 Display.getDefault().asyncExec(new Runnable() {
167                                         public void run() {
168                                                 String html = getHtmlSource(funcName);
169                                                 browser.setText(html);
170                                         }
171                                 });
172                         }
173                 }).start();
174         }
175
176         /**
177          * Filters the function's reference page extracting only parts of it
178          * 
179          * @param source HTML source of the reference page
180          * @return HTML source of reference page
181          */
182         private String filterHtmlSource(String source) {
183                 try {
184                         Parser parser = new Parser(source);
185                         String [] tagsToBeFound = {"DIV"};
186                         ArrayList classList = new ArrayList(8);
187                         classList.add("refnamediv");
188                         classList.add("refsect1 description");
189                         classList.add("refsect1 parameters");
190                         classList.add("refsect1 returnvalues");
191                         classList.add("refsect1 examples");
192                         classList.add("refsect1 seealso");
193                         classList.add("refsect1 u");
194                         TagFindingVisitor visitor = new TagFindingVisitor(tagsToBeFound);
195                         parser.visitAllNodesWith(visitor);
196                         Node [] allPTags = visitor.getTags(0);
197                         StringBuffer output = new StringBuffer();
198                         for (int i = 0; i < allPTags.length; i++) {
199                                 String tagClass = ((Div)allPTags[i]).getAttribute("class");
200                                 if (classList.contains(tagClass)) {
201                                         output.append(allPTags[i].toHtml());
202                                 }
203                         }
204                         return output.toString().replaceAll("—", "-");
205                         //.replace("<h3 class=\"title\">Description</h3>", " ");
206                 } catch (ParserException e) {
207                         e.printStackTrace();
208                 }
209                 return "";
210         }
211
212         /**
213          * Reads the template that defines the style of the reference page
214          * shown inside the view's browser
215          * 
216          * @return HTML source of the template
217          */
218         public String getRefPageTemplate() {
219                 Bundle bundle = Platform.getBundle(PHPManualUIPlugin.PLUGIN_ID);
220                 URL fileURL = Platform.find(bundle, new Path("templates"));
221                 StringBuffer contents = new StringBuffer();
222                 BufferedReader input = null;
223                 try {
224                         URL resolve = Platform.resolve(fileURL);
225                         input = new BufferedReader(new FileReader(resolve.getPath()+"/refpage.html"));
226                         String line = null;
227                         while ((line = input.readLine()) != null){
228                                 contents.append(line);
229                         }
230                 }
231                 catch (FileNotFoundException e) {
232                         e.printStackTrace();
233                 } catch (IOException e) {
234                         e.printStackTrace();
235                 }
236                 finally {
237                         try {
238                                 if (input!= null) {
239                                         input.close();
240                                 }
241                         }
242                         catch (IOException ex) {
243                                 ex.printStackTrace();
244                         }
245                 }
246                 return contents.toString();
247         }
248
249         /**
250          * Replaces each substring of source string that matches the
251          * given pattern string with the given replace string
252          * 
253          * @param source The source string
254          * @param pattern The pattern string
255          * @param replace The replace string
256          * @return The resulting String
257          */
258         public static String replace(String source, String pattern, String replace) {
259                 if (source != null) {
260                         final int len = pattern.length();
261                         StringBuffer sb = new StringBuffer();
262                         int found = -1;
263                         int start = 0;
264                         while ((found = source.indexOf(pattern, start)) != -1) {
265                                 sb.append(source.substring(start, found));
266                                 sb.append(replace);
267                                 start = found + len;
268                         }
269                         sb.append(source.substring(start));
270                         return sb.toString();
271                 } else {
272                         return "";
273                 }
274         }
275
276         /**
277          * Looks for the function's reference page inside the doc.zip file and
278          * returns a filtered HTML source of it embedded in the template
279          * 
280          * @param funcName
281          *            Function name
282          * @return HTML source of reference page
283          */
284         public String getHtmlSource(String funcName) {
285                 Bundle bundle = Platform.getBundle(PHPHelpPlugin.PLUGIN_ID);
286                 URL fileURL = Platform.find(bundle, docPath);
287                 byte[] b = null;
288                 try {
289                         URL resolve = Platform.resolve(fileURL);
290                         ZipFile docFile = new ZipFile(resolve.getPath());
291                         ZipEntry entry = docFile.getEntry("doc/function."+funcName.replace('_', '-')+".html");
292                         InputStream ref = docFile.getInputStream(entry);
293                         b = new byte[(int)entry.getSize()];
294                         ref.read(b, 0, (int)entry.getSize());
295                         if (b != null) {
296                                 String reference = filterHtmlSource(new String(b));
297                                 String refPageTpl = getRefPageTemplate();
298                                 refPageTpl = refPageTpl.replaceAll("%title%", funcName);
299                                 refPageTpl = replace(refPageTpl, "%reference%", reference);
300                                 return refPageTpl;
301                         }
302                 } catch (IOException e) {
303                         return "<html></html>";
304                 } catch (Exception e) {
305                         return null;
306                 }
307                 return "<html></html>";
308         }
309
310         /**
311          * Returns the currently active java editor, or <code>null</code> if it
312          * cannot be determined.
313          * 
314          * @return the currently active java editor, or <code>null</code>
315          */
316         private PHPEditor getJavaEditor() {
317                 try {
318                         IEditorPart part = PHPeclipsePlugin.getActivePage().getActiveEditor();
319                         if (part instanceof PHPEditor)
320                                 return (PHPEditor) part;
321                         else
322                                 return null;
323                 } catch (Exception e) {
324                         return null;
325                 }
326         }
327
328 }