terça-feira, 28 de abril de 2015

Plug-in do Eclipse, é possível criar um ?

Sejam bem vindos ao Preciso Estudar Sempre. Hoje falaremos sobre plugins no Eclipse e como é fácil construir um. Para que você entenda o que iremos abordar aqui, é necessário que tenha algum conhecimento sobre Java, OO e Eclipse.

Na sua opinião, antes de você ler o parágrafo acima, você achava que era possível a construção de plugins para o Eclipse ?

Sim ? Não ? 

Você deve estar pensando agora: "Ainnn Joãooowww, essas empresas que fazem esses plugins, possuem super laboratórios com tecnologia super avançada, não dá pra eu fazer o meu plugin para Eclipse com o meu notebookizinho meia boca".

Então eu lhe respondo: "Você está errado meu amigo. É possível sim fazer seu próprio plugin e ainda lhe digo mais, é muito fácil".

Caso você esteja absurdamente ávido para respostas que irão mudar sua vida, clique aqui e baixe o projeto pronto.

O nosso plugin será simples, ele irá procurar por uma palavra específica em arquivos dentro de uma pasta e exibir os resultados em uma view.

Vamos por as mãos na massa ?

1 - Crie uma pasta chamada arquivos, no lugar onde você quiser. Lembre-se que será nesse lugar que o plugin irá realizar as buscas.

2 - Crie uma pasta chamada workspace. Essa pasta será o workspace de desenvolvimento do plugin.

3 - Abra o Eclipse apontando a pasta criada no passo 2 como workspace.

4 - Siga os seguinte passos: File > New > Other > Plug-in Project

Figura 1 - Criação do plugin
5 - Ponha um nome para o seu plugin e clique em Next.
Figura 2 - Nome do plugin
6 - Clique em Next.
Figura 3 - Content
7 - A tela de Templates abrirá. Essa tela é muito importante porque definiremos como nosso plugin será. O Eclipse já provê vários templates prontos para o uso. Como iremos criar simplesmente um plugin de view, clique em Plug-in with a view. Quando você clica no template, é mostrado à direita sua descrição e que extensões são usadas. Clique em Next.
Figura 4 - Templates
8 - Depois da tela de templates vem a tela de configurações principais. Essa tela é muito importante, é nela que definiremos a categoria em que nosso plugin se enquadrará, qual será o nome da classe que representará o plugin e o viewer type. O viewer type é o tipo de visualização dos dados. Como queremos que nosso plugin se pareça com o search do Eclipse, escolheremos o modelo Tree viewer. Clique em Finish.
Figura 5 - Configurações principais
Pronto !!!!!! Nosso plugin está pronto e você deve estar se sentindo assim.


Até agora não programamos nada e já criamos um plugin. Lembra que citei no início no post o quanto era fácil criar um plugin para eclipse ? Então, você vê agora na prática.

Vamos ver funcionando ?

Clique com o botão direito em cima do seu projeto e siga os seguintes passos: Clique com o botão direito > Run As > Eclipse Application.

Figura 6 - Executando o plugin
Quando você executar, vai notar que um segundo Eclipse é aberto. Sim, você leu isso mesmo. Isto não está errado pois, você está desenvolvendo parte da ferramenta então, nada mais justo que uma nova instância da ferramenta, com suas alterações, seja criada.

Após o Eclipse ter sido aberto, seu plugin será executado. Caso a execução automática não esteja funcionando, siga os seguintes passos: Window > Show View > Other > Escolha sua categoria e duplo clique no plugin.

Lembra daquela categoria que eu tinha citado acima ? Então, ela entra agora. Quando a tela de views é aberta, a separação é feita por categorias e o nome que você escolheu estará lá.

O resultado da primeira execução é exibido abaixo.
Figura 7 - Resultado da primeira execução
Você notou que o nosso plugin já veio com algum conteúdo pré-definido. Tudo na view é customizável e é isso que faremos. Crie a classe Localizador na package de views.

Classe Localizador
 package precisoestudarsempreplugin.views;  

 import java.io.BufferedReader; 
 import java.io.File; 
 import java.io.FileNotFoundException; 
 import java.io.FileReader; 
 import java.io.IOException; 
 import java.util.ArrayList; 
 import java.util.HashMap; 
 import java.util.List; 
 import java.util.Map; 

 public class Localizador { 

      public Map<String,List<String>> localizar(){ 
           Map<String,List<String>> ocorrencias = new HashMap<String, List<String>>(); 
           File folder = new File("C:\\arquivos"); 
           for(File file : folder.listFiles()){ 
                ocorrencias.put(file.getName(), this.analisarArquivo(file)); 
           } 
           return ocorrencias; 
      }

      private List<String> analisarArquivo(File file){ 
           List<String> ocorrencias = new ArrayList<String>(); 
           BufferedReader bufferedReader = null; 
           try{ 
                int lineCount=0; 
                String palavraProcurada = "Preciso Estudar Sempre"; 
                bufferedReader = new BufferedReader(new FileReader(file)); 
                while(bufferedReader.ready()){ 
                     ++lineCount; 
                     String line = bufferedReader.readLine(); 
                     if(line.toLowerCase().contains(palavraProcurada.toLowerCase())){ 
                          ocorrencias.add(lineCount + ": " + line); 
                     } 
                } 
           } catch (FileNotFoundException e) { 
                e.printStackTrace(); 
           } catch (IOException e) { 
                e.printStackTrace(); 
           } finally { 
                if(bufferedReader != null) { 
                     try { 
                          bufferedReader.close(); 
                     } catch (IOException e) { 
                          e.printStackTrace(); 
                     } 
                } 
           } 
           return ocorrencias; 
      } 
 } 

Agora, modifique o conteúdo da classe do plugin, no nosso caso é a classe MeuPlugin.

 package precisoestudarsempreplugin.views;  
 import java.util.ArrayList; 
 import java.util.List; 
 import java.util.Map; 
 import java.util.Set; 
 import org.eclipse.core.runtime.IAdaptable; 
 import org.eclipse.jface.action.Action; 
 import org.eclipse.jface.action.IMenuListener; 
 import org.eclipse.jface.action.IMenuManager; 
 import org.eclipse.jface.action.IToolBarManager; 
 import org.eclipse.jface.action.MenuManager; 
 import org.eclipse.jface.action.Separator; 
 import org.eclipse.jface.viewers.IStructuredContentProvider; 
 import org.eclipse.jface.viewers.ITreeContentProvider; 
 import org.eclipse.jface.viewers.LabelProvider; 
 import org.eclipse.jface.viewers.TreeViewer; 
 import org.eclipse.jface.viewers.Viewer; 
 import org.eclipse.jface.viewers.ViewerSorter; 
 import org.eclipse.swt.SWT; 
 import org.eclipse.swt.graphics.Image; 
 import org.eclipse.swt.widgets.Composite; 
 import org.eclipse.swt.widgets.Menu; 
 import org.eclipse.ui.IActionBars; 
 import org.eclipse.ui.ISharedImages; 
 import org.eclipse.ui.IWorkbenchActionConstants; 
 import org.eclipse.ui.PlatformUI; 
 import org.eclipse.ui.part.DrillDownAdapter; 
 import org.eclipse.ui.part.ViewPart; 

 /** 
  * This sample class demonstrates how to plug-in a new 
  * workbench view. The view shows data obtained from the 
  * model. The sample creates a dummy model on the fly, 
  * but a real implementation would connect to the model 
  * available either in this or another plug-in (e.g. the workspace). 
  * The view is connected to the model using a content provider. 
  * <p> 
  * The view uses a label provider to define how model 
  * objects should be presented in the view. Each 
  * view can present the same model objects using 
  * different labels and icons, if needed. Alternatively, 
  * a single label provider can be shared between views 
  * in order to ensure that objects of the same type are 
  * presented in the same way everywhere. 
  * <p> 
  */ 

 public class MeuPlugin extends ViewPart { 

      /** 
       * The ID of the view as specified by the extension. 
       */ 
      public static final String ID = "precisoestudarsempreplugin.views.MeuPlugin"; 
      private TreeViewer viewer; 
      private DrillDownAdapter drillDownAdapter; 
      private Action action1; 
      private Action action2; 

      /* 
       * The content provider class is responsible for 
       * providing objects to the view. It can wrap 
       * existing objects in adapters or simply return 
       * objects as-is. These objects may be sensitive 
       * to the current input of the view, or ignore 
       * it and always show the same content 
       * (like Task List, for example). 
       */ 

      class TreeObject implements IAdaptable { 

           private String name; 
           private TreeParent parent; 

           public TreeObject(String name) { 
                this.name = name; 
           } 

           public String getName() { 
                return name; 
           } 

           public void setParent(TreeParent parent) { 
                this.parent = parent; 
           } 

           public TreeParent getParent() { 
                return parent; 
           } 

           public String toString() { 
                return getName(); 
           } 

           public Object getAdapter(Class key) { 
                return null; 
           } 
      } 

      class TreeParent extends TreeObject { 

           private ArrayList children; 

           public TreeParent(String name) { 
                super(name); 
                children = new ArrayList(); 
           } 

           public void addChild(TreeObject child) { 
                children.add(child); 
                child.setParent(this); 
           } 

           public void removeChild(TreeObject child) { 
                children.remove(child); 
                child.setParent(null); 
           } 

           public TreeObject [] getChildren() { 
                return (TreeObject [])children.toArray(new TreeObject[children.size()]); 
           } 

           public boolean hasChildren() { 
                return children.size()>0; 
           } 
      } 

      class ViewContentProvider implements IStructuredContentProvider, 
                                                     ITreeContentProvider { 
           private TreeParent invisibleRoot; 

           public void inputChanged(Viewer v, Object oldInput, Object newInput) { 

           } 

           public void dispose() { 

           } 

           public Object[] getElements(Object parent) { 
                if (parent.equals(getViewSite())) { 
                     if (invisibleRoot==null) initialize(); 
                     return getChildren(invisibleRoot); 
                } 
                return getChildren(parent); 
           } 

           public Object getParent(Object child) { 
                if (child instanceof TreeObject) { 
                     return ((TreeObject)child).getParent(); 
                } 
                return null; 
           } 

           public Object [] getChildren(Object parent) { 
                if (parent instanceof TreeParent) { 
                     return ((TreeParent)parent).getChildren(); 
                } 
                return new Object[0]; 
           } 

           public boolean hasChildren(Object parent) { 
                if (parent instanceof TreeParent) 
                     return ((TreeParent)parent).hasChildren(); 
                return false; 
           } 

 /* 
  * We will set up a dummy model to initialize tree heararchy. 
  * In a real code, you will connect to a real model and 
  * expose its hierarchy. 
  */ 
           private void initialize() { 
                invisibleRoot = new TreeParent(""); 
                Localizador localizador = new Localizador(); 
                Map<String,List<String>> ocorrencias = localizador.localizar(); 
                Set<String> keys = ocorrencias.keySet(); 
                for(String key : keys){ 
                     if(!ocorrencias.get(key).isEmpty()){ 
                          TreeParent fileRoot = new TreeParent(key); 
                          for(String ocorrencia : ocorrencias.get(key)){ 
                               TreeObject nodeOcorrencia = new TreeObject(ocorrencia); 
                               fileRoot.addChild(nodeOcorrencia); 
                          } 
                          invisibleRoot.addChild(fileRoot); 
                     } 
                } 
           } 
      } 

      class ViewLabelProvider extends LabelProvider { 

           public String getText(Object obj) { 
                return obj.toString(); 
           } 

           public Image getImage(Object obj) { 
                String imageKey = ISharedImages.IMG_TOOL_FORWARD; 
                if (obj instanceof TreeParent) 
                  imageKey = ISharedImages.IMG_OBJ_FILE; 
                return PlatformUI.getWorkbench().getSharedImages().getImage(imageKey); 
           } 
      } 

      class NameSorter extends ViewerSorter { 

      } 

      /** 
       * The constructor. 
       */ 
      public MeuPlugin() { 

      } 

      /** 
       * This is a callback that will allow us 
       * to create the viewer and initialize it. 
       */ 
      public void createPartControl(Composite parent) { 
           viewer = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL); 
           drillDownAdapter = new DrillDownAdapter(viewer); 
           viewer.setContentProvider(new ViewContentProvider()); 
           viewer.setLabelProvider(new ViewLabelProvider()); 
           viewer.setSorter(new NameSorter()); 
           viewer.setInput(getViewSite()); 
           // Create the help context id for the viewer's control 
           PlatformUI.getWorkbench().getHelpSystem().setHelp(viewer.getControl(), "Teste.viewer"); 
           makeActions(); 
           hookContextMenu(); 
           contributeToActionBars(); 
      } 

      private void hookContextMenu() { 
           MenuManager menuMgr = new MenuManager("#PopupMenu"); 
           menuMgr.setRemoveAllWhenShown(true); 
           menuMgr.addMenuListener(new IMenuListener() { 
                public void menuAboutToShow(IMenuManager manager) { 
                     MeuPlugin.this.fillContextMenu(manager); 
                } 
           }); 
           Menu menu = menuMgr.createContextMenu(viewer.getControl()); 
           viewer.getControl().setMenu(menu); 
           getSite().registerContextMenu(menuMgr, viewer); 
      } 

      private void contributeToActionBars() { 
           IActionBars bars = getViewSite().getActionBars(); 
           fillLocalPullDown(bars.getMenuManager()); 
           fillLocalToolBar(bars.getToolBarManager()); 
      } 

      private void fillLocalPullDown(IMenuManager manager) { 
           manager.add(action1); 
           manager.add(new Separator()); 
           manager.add(action2); 
      } 

      private void fillContextMenu(IMenuManager manager) { 
           manager.add(action1);
           manager.add(action2); 
           manager.add(new Separator()); 
           drillDownAdapter.addNavigationActions(manager); 
           // Other plug-ins can contribute there actions here 
           manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS)); 
      } 

      private void fillLocalToolBar(IToolBarManager manager) { 
           manager.add(action1); 
           manager.add(action2); 
           manager.add(new Separator()); 
           drillDownAdapter.addNavigationActions(manager); 
      } 

      private void makeActions() { 
           action1 = new Action() { 
                public void run() { 
                     viewer.expandAll(); 
                } 
           }; 
           action1.setText("Expand All"); 
           action1.setToolTipText("Expand All"); 
           action1.setImageDescriptor(PlatformUI.getWorkbench().getSharedImages(). 
                getImageDescriptor(ISharedImages.IMG_OBJS_INFO_TSK)); 
           action2 = new Action() { 
                public void run() { 
                     viewer.collapseAll(); 
                } 
           }; 
           action2.setText("Collapse All"); 
           action2.setToolTipText("Collapse All"); 
           action2.setImageDescriptor(PlatformUI.getWorkbench().getSharedImages(). 
                     getImageDescriptor(ISharedImages.IMG_OBJS_INFO_TSK)); 
      } 

      /** 
       * Passing the focus request to the viewer's control. 
       */ 
      public void setFocus() { 
           viewer.getControl().setFocus(); 
      } 
 } 

Agora vamos entender o que está escrito.
  • No método initialize é onde toda a magia acontece, é nele aonde são criados os nós da árvore (lembra o tipo de visualização que escolhemos ?). Cada nó possui o conteúdo que queremos exibir.
  • As classes TreeParent TreeObject são innerclasses, se você não sabe o que é isso, clique aqui e dê uma olhada.
  • No método getImage definimos os ícones para as raízes e folhas da árvore. Os ícones usados no nosso plugin são os mesmos usados no Eclipse. Os ícones são representados através de constantes da classe ISharedImages.
  • Nos métodos fillLocalPullDownfillContextMenu é definido a disposição dos botões do plugin. A diferença entre esses métodos é que o primeiro se refere a seta ao lado do minimizar e o segundo aos botões da barra.
Figura 8 - Nossos botões
  • No método makeActions são definidos os tooltips, nomes, as ações e ícones dos botões. No nosso caso, os botões irão expandir e colapsar todos os resultados.
DESAFIO

Nesse post deixo dois desafios e quem conseguir resolver, bote as soluções nos comentários pois, eu não consegui. O primeiro desafio é por o íconeem um dos botões. O segundo desafio é por o highlight nas palavras encontradas.

Sugestões ?! Críticas ?! Elogios ?! 

Deixe aí nos comentários ou na nossa página do facebook.


Referências:
Tutorial - Como criar um plug-in para o Eclipse: https://www.youtube.com/watch?v=4HubyNCIHg4
Nested Classes: https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html

4 comentários:

Luis Marcelo Bruckner disse...

Maida, parabéns pelo post. Muito bacana saber que dá pra criar plugins no Eclipse.
Abs

Preciso estudar sempre disse...

Obrigado Luís.

Unknown disse...

Muito bom, parabéns!

Preciso estudar sempre disse...

Obrigado Wellington !!