Archivo

Entradas Etiquetadas ‘swing’

Como enviar Tweets desde Java usando Twitter4J

Domingo, 5 de julio de 2009

Una de las cosas que quiero hacer para la versión 1.1 de StupidZombie es agregarle soporte para actualizar el estado de Twitter cada vez que hacemos un ping. Como siempre es el asunto de construir algo desde cero o utilizar una herramienta existente y en el caso de StupidZombie lo que quiero es implementar la funcionalidad lo más rápido posible (por ejemplo, no tengo tiempo para seguir los cambios en el API de Twitter).

Después de buscar en la red me conseguí que Twitter4J es quizas la versión más madura para Java (recuerden, StupidZombie está escrito en ese lenguaje). Ni corto ni perezoso me puse a echar código y al final me decidí escribir una pequeña aplicación en Swing la cual hace lo siguiente:

  1. Autoriza la aplicación contra su cuenta de Twitter
  2. Obtiene un PIN y claves especiales de autorización (lo cual no es lo mismo que su usuario clave, a eso se le conoce como OAuth).
  3. Envia un mensaje (tweet) a su cuenta en Twitter desde la aplicación en Java

Nota, si usted es el desarrollador de la aplicación entonces lo primero que hay que hacer es registrar una aplicación nueva en Twitter (sus usuarios pueden saltarse este paso). Una vez terminado se ve como lo siguiente:

Registered applications
Aplicaciones registradas en Twitter

Una vez registrada debemos pasar “Consumer key” y “Consumer Secret” a nuestro código de Twitter4j, yo lo hago en el constructor:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
	private TwitterPing(String consumerKey, String consumerSecret) throws TwitterException {
		super("KodeGeek simple Twitter pinger");
		if (consumerKey == null) {
			throw new IllegalArgumentException("Consumer key is missing");
		}
		if (consumerSecret == null) {
			throw new IllegalArgumentException("Consumer secret is missing");
		}
		twitter = new Twitter();
		twitter.setOAuthConsumer(consumerKey, consumerSecret);
		requestToken = twitter.getOAuthRequestToken();
 
		setPreferredSize(new Dimension(600, 400));
 
	}

Desde la aplicación en Swing hacemos clic en el botón que dice “Authorize KodeGeek on Tweeter”. Si el soporte de Java para escritorio está activado entonces el navegador por omisión se arrancará e iremos a Twitter:

Allowing KodeGeek
Hora de autorizar a KodeGeek :)

El código que hace esto es super sencillo :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
	/**
	 * Send the user to the authorization webpage
	 * @throws Exception 
	 */
	private void sendUserToAuthUrl() throws Exception {
		final TwitterPing instance = this;
		log.log(Level.INFO, String.format("Got authorization URL: %s", requestToken.getAuthorizationURL()));
		if (Desktop.isDesktopSupported()) {
			Desktop.getDesktop().browse(new URI(requestToken.getAuthorizationURL()));
			log.info("Redirection was successfull.");
		} else {
			EventQueue.invokeLater(new Runnable() {
 
				@Override
				public void run() {
					JOptionPane.showMessageDialog(
							instance, 
							String.format("Cannot call default browser, please go to this URL instead: %s", requestToken.getAuthorizationURL()), 
							"Problems trying to send the user to default page", 
							JOptionPane.ERROR_MESSAGE);
				}
			});
		}
	}

Si la autorización es aceptada (como se ve a continuación):

KodeGeek twitter application
La autorización trabajó

Entonces podemos pedir el token de acceso usando el PIN obtenido en el paso anterior:

1
2
3
4
5
6
7
8
9
10
	/**
	 * Call this method only after the user has authorized the application
	 * @throws TwitterException 
	 */
	private void getAccessToken(final String pin) throws TwitterException {
		log.info(String.format("Got token: %s, Got secret: %s", requestToken.getToken(), requestToken.getTokenSecret()));
		accessToken = twitter.getOAuthAccessToken(requestToken, pin);
		if (accessToken != null)
			log.info(String.format("Got access token for user %s", accessToken.getScreenName()));
	}

Lo cual se ve así:

Ping number, required for desktop appsNúmero PIN para darle acceso a nuestro cliente a la cuenta de Twitter

Si todo va bien entonces podemos mostrar los 3 pedazos de la autorización necesarios para poder enviar un Tweet:

After geting the auth tokens
La aplicación tiene todo lo que necesita. Hora de enviar un tweet

Ya hacer el tweet es trivial con el siguiente pedazo de código:

1
2
3
4
5
6
7
8
9
10
	/**
	 * Send a tween using an existing AccessToken
	 * @param tweet The update to set
	 * @throws TwitterException If there is a problem updating the status
	 * @return The status of the tweet
	 */
	private Status sendTweet(final String tweet) throws TwitterException {
		twitter.setOAuthAccessToken(accessToken);
		return twitter.updateStatus(tweet);
	}

Al final, ¡exito!:

Success, tweet update
Como se ve nuestro mensaje en Twitter, enviado desde Java

El protocolo de Twitter soporta muchísimas cosas más. En particular le recomiendo que se lean la documentación relacionada para entender más como trabajan las cosas, en especial la autenticación usando OAth. Ahhh, y por supuesto aquí les dejo el código fuente completo para que se diviertan echando código.

Hasta la siguiente entrega, dentro de unas horas me sale ir a una parrillada :)

Veneblogs: , , , ,

Blogalaxia: , , , ,

To2Blogs: , , , ,

Technorati: , , , ,

Del.icio.us: , , , ,

java, kodegeek, opensource, programación , , , , , ,

Localización en Java (II): El caso de StupidZombie

Domingo, 21 de junio de 2009

StupidZombie localization issues¿Qué tiene de malo la imagen?. Pista: ¡Llámame p’atras!

Bueno, ya casi está listo. Pero cuando digo ya casi es porque aún tengo componentes de Swing los cuales no se portan muy bien en cuanto le cambio el lenguaje y la región al programa.

¿Qué hacer? Parece que es una cucaracha común, en especial con JFileChooser:

Hay soluciones, voy a probarlas y después les digo como me fué. Sin embargo con Swing es irritante ver como la promesa de Java “Run anywhere” se quiebra un poquito ;)

Veneblogs: , , , , ,

Blogalaxia: , , , , ,

To2Blogs: , , , , ,

Technorati: , , , , ,

Del.icio.us: , , , , ,

java , , , , ,

Agregando un botón a la celda de una tabla usando Swing

Jueves, 9 de abril de 2009

Parece trivial (y una vez que se resuelven todos los pormenores lo es), Sin embargo un problema de “rendering” me quebró la cabeza por dos buenos días, hasta que al fin (y con la ayuda de buenos desarrolladores en Internet) le logré dar la vuelta.

A la final el código quedó asi:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
import java.util.Date;
import java.util.Random;
import java.util.Vector;
import javax.swing.JButton;
import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.JTable;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.AbstractCellEditor;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.UIManager;
import javax.swing.table.TableCellEditor;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.text.SimpleDateFormat;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
 
public class GuiWithProblems extends JFrame {
	private static final long serialVersionUID = 1L;
	private DataTableModel model;
 
	public enum TableHeader {
		Date(Date.class), Counter(Integer.class), Name(String.class);
		Class< ?> currClass;
		private TableHeader(Class < ?>currClass) {this.currClass = currClass;}
		public Class < ?>getCurrClass() {return currClass;}
	}
 
	@SuppressWarnings("unchecked")
	public GuiWithProblems() {
		super("Simple GUI with JTable");
		setPreferredSize(new Dimension(600, 500));
		setLayout(new BorderLayout());
		setDefaultCloseOperation(EXIT_ON_CLOSE);
		@SuppressWarnings("unused")
		Vector< ?> data = new Vector();
		model = new DataTableModel();		
		JTable table = new JTable();
		table.setModel(model);
		table.setColumnSelectionAllowed(false);
		table.setRowSelectionAllowed(true);
		TableCellRenderer render = new GuiCellRenderer();
		TableColumn col = table.getColumnModel().getColumn(TableHeader.Name.ordinal());
		JButton editorBtn = new JButton();
		col.setCellEditor(new GuiCellEditor(table));
		col.setCellRenderer(new NameBtnCellRenderer(editorBtn));
		table.setDefaultRenderer(Date.class, render);
		table.setDefaultRenderer(Double.class, render);
		table.setDefaultRenderer(Long.class, render);
		table.setDefaultRenderer(Integer.class, render);
		table.setDefaultRenderer(String.class, render);
		getContentPane().add(new JScrollPane(table), BorderLayout.CENTER);
	}
 
	@SuppressWarnings("unchecked")
	private void addData() {
		Random random = new Random(new Date().getTime());
		for (int i = 0; i < 20; i++) {
			Vector data = new Vector();
			data.add(TableHeader.Date.ordinal(), new Date());
			data.add(TableHeader.Counter.ordinal(), i);
			data.add(TableHeader.Name.ordinal(), random.nextInt() + "");
			model.add(data);	
		}
	}
 
	final void showGui() {
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {	pack();	setVisible(true);}
		});
	}
 
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		final GuiWithProblems instance = new GuiWithProblems();
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				instance.showGui();
				instance.addData();
			}
		});
 
	}
 
	public class DataTableModel extends AbstractTableModel {
		private static final long serialVersionUID = 769042563295665904L;
		@SuppressWarnings("unchecked")
		private Vector data;
 
		@SuppressWarnings("unchecked")
		public DataTableModel() {
			data = new Vector();
		}
 
		@Override
		public Class<?> getColumnClass(int columnIndex) {return TableHeader.values()[columnIndex].getCurrClass();}
 
		@Override
		public int getRowCount() {return data.size();}
 
		@SuppressWarnings("unchecked")
		@Override
		public Object getValueAt(int rowIndex, int columnIndex) {
			Vector elem = (Vector) data.get(rowIndex);
			switch (TableHeader.values()[columnIndex]) {
			case Date: 
				return elem.get(TableHeader.Date.ordinal());
			case Counter: 
				return elem.get(TableHeader.Counter.ordinal());
			case Name:
				return elem.get(TableHeader.Name.ordinal());
			default: 
				return null;
			}
		}
 
		@Override
		public int getColumnCount() {return TableHeader.values().length;}
 
		@Override
		public String getColumnName(int column) {return TableHeader.values()[column].name();}
 
		@Override
		public boolean isCellEditable(int row, int column) {
			if (column == TableHeader.Name.ordinal()) return true; // ENABLE THE EDITOR JUST FOR THE NAME
			return false;
		}
 
		public void add(final Object elem) {
			SwingUtilities.invokeLater(new Runnable() {
				@SuppressWarnings("unchecked")
				public void run() {
					data.add(elem);
					int rows = getRowCount() - 1;
					fireTableRowsInserted(rows, rows);
				} 
			});
		}
	}
	// General editor
	class GuiCellRenderer extends DefaultTableCellRenderer  {
		private static final long serialVersionUID = 211718743340765799L;
		private SimpleDateFormat dForm;
		public GuiCellRenderer() {
			super();
			dForm = new SimpleDateFormat("hh:mm:ss a");
		}
 
		@Override
		public Component getTableCellRendererComponent(JTable table, Object val, boolean select, boolean focus, int row, int col) {
			super.getTableCellRendererComponent(table, val, select, focus, row, col); // Make sure gets called
			switch (TableHeader.values()[table.convertColumnIndexToModel(col)]) {
			case Date:
				setText(dForm.format((Date)val));
				break;
			case Counter:
				Integer iVal = (Integer) val;
				setText(iVal.toString());
				break;
			case Name:
				setText((String) val);
				break;
			default:
				setText(val.toString());
				break;
			}
			return this;
		}
	}
	// Renderer used only for the name, renders column as button
	class NameBtnCellRenderer extends DefaultTableCellRenderer  {
		private static final long serialVersionUID = 2L;
		private JButton btn;
		public NameBtnCellRenderer(final JButton aBtn) {
			super();
			btn = aBtn;
			btn.setFocusPainted(false);
		}
 
		@Override
		public Component getTableCellRendererComponent(JTable table, Object val, boolean select, boolean focus, int row, int col) {
			super.getTableCellRendererComponent(table, val, select, focus, row, col);
            if (focus)  {
            	btn.setForeground(table.getForeground());
            	btn.setBackground(UIManager.getColor("Button.background"));
            }
            else if (select) {
            	btn.setForeground(table.getSelectionForeground());
            	btn.setBackground(table.getSelectionBackground());
            } else {
            	btn.setForeground(table.getForeground());
            	btn.setBackground(UIManager.getColor("Button.background"));
            }
 
            btn.setText( (val == null) ? "" : val.toString() );
            return btn;
		}
	}
	// Cell editor for the name (button)
	public final class GuiCellEditor extends  AbstractCellEditor implements TableCellEditor, ActionListener {
		private static final long serialVersionUID = 9L;
		private JButton btn;
		private Component comp;
		public GuiCellEditor(final Component aComp) {
			btn = new JButton();
			btn.addActionListener(this);
			comp = aComp;
		}
 
		@Override
		public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
			String val = table.getValueAt(row, column) == null? "": table.getValueAt(row, column).toString();
			btn.setText(val);
			return btn;
		}
 
		@Override
		public Object getCellEditorValue() { return btn.getText(); }
 
		@Override		
		public void actionPerformed(ActionEvent e) {
			SwingUtilities.invokeLater(new Runnable() {
				@Override
				public void run() {
					SwingUtilities.invokeLater(new Runnable() {
						@Override
						public void run() {
							fireEditingStopped();
							JOptionPane.showMessageDialog(comp, String.format("Not much to show, %s", btn.getText()), "About", JOptionPane.INFORMATION_MESSAGE, null);
						}
					});
 
				}
			});
		}
	}
 
}

Les dejó el código para que lo disfruten, aún tiene un bug en OSX (Linux y Windows no tienen el problema). Espero sus comentarios como siempre :)

Veneblogs: , , ,

Blogalaxia: , , ,

To2blogs: , , ,

Technorati: , , ,

Del.icio.us: , , ,

java, kodegeek, programación , , ,

Java y Swing: Cuando el rehuso no es lo mejor

Domingo, 17 de agosto de 2008

Ha pasado un rato bien largo desde que escribo en el blog; La razón principal es que he estado estudiando (en algún momento les comentaré para qué) y decidí concentrarme únicamente en eso y en el trabajo.

Cero distracciones.

Lo otro es que ahora estoy haciendo algo que no habia hecho antes: Aplicaciones de escritorio en Java. Si bien el lenguaje es el mismo, las implicaciones son diferentes de cuando se hace una aplicación del lado del servidor o aplicaciones web.

Los operadores de piso (traders) con quien trabajo empezaron nuevos negocios y varios de ellos pedian aplicaciones en Swing. Incialmente comenzé con una muy sencilla, escrita totalmente desde cero la cual recibe ordenes que los corredores de bolsa hacen las cuales deben ser aprobadas por los operadores (me disculpan si los términos no son exactos pero es que me da una flojera enorme de hacer spanglish :) ). De allí alguien se dió cuenta que podía ayudarlos a acomodar la aplicación de trading principal.

En este caso, el componente que tenia problemas trabajaba bien … hasta la 1:00 de la tarde. Después de allí fallaba por falta de memoria y los analistas no odian ver sus transacciones en tiempo real de ahi en adelante.

La aplicación original es impresionante, con un montón de aditamento que la hacen sofisticada aún el día de hoy, 5 años después que fué creada (la aplicación ha tenido varias reencarnaciones, cada una de ellas con más funcionalidad). El problema es que tiene capas, capas y más capas de código hecho en casa el cual ya no es compatible con Java 1.6 (de hecho, aún tenemos que correr la mayor parte de esta en una versión anterior de la máquina virtual).

¿Cual fué la solución? Me pidieron que volviera a escribir esa parte de la aplicación (sin otros detalles) en una tarde. La única forma que se me ocurrió fué escribirla a mi manera usando solamente Swing, ningún aditamento.

La aplicación comenzó como un prototipo, del cual todos estabamos algo temerosos (400 mil entradas al día y desempeño rápido eran los requerimientos). De allí cosas básicas como ordenar y ocultar columnas, filtros, exportar los datos a Excel (Si, si la aplicación tiene tablas) entre otros se portaron muy bien. Hubo que tener en cuenta problemas de concurrencia (ya que la aplicación es de alto número de transacciones, además de que debe mostrar todas las operaciones del dia, haciendo una repetición de todas las que hayan ocurrido antes de arrancar el programa).

El nuevo componente no sólo muestra 500 mil transacciones sin problema, sino que además es más rápido (ya lo usamos en días de alto volumen y con expiración de opciones. Trabajó sin chistar). Agregarle la funcionalidad de la aplicación vieja no fué tan difícil y resulta que ahora los operadores con los que trabajo se mudaron a la nueva aplicación.

Y aún sigo aprendiendo cosas nuevas de Swing. Lo último que descubrí es que soporta pieles, usando Synth y que la integración nativa con el escritorio ha mejorado mucho.

Supongo que esta es una de esas ocasiones en donde es mejor “quemar al rancho” y volver a construir desde cero.

Ya les comentare más, pero si quieren leer sobre mis andanzas estos días metanse en los foros de Java Swing de Sun :) .

Buscar en otros sitios:

Blogalaxia:, , , , ,
Technorati:, , , , ,
To2blogs:, , , , ,
Del.icio.us:, , , , ,

java , , , , ,

RowSorter, "AWT-EventQueue-0" java.lang.ArrayIndexOutOfBoundsException

Sábado, 2 de agosto de 2008

Les tengo una pregunta a todos los gurus de Java: ¿Cual es el problema con JTable y el uso de RowSorter?

Más detalles aquí, gracias de antemano a quien tenga la respuesta (yo corpartire mi descubrimientos).

Buscar en otros sitios:

Blogalaxia:, ,
Technorati:, ,
To2blogs:, ,
Del.icio.us:, ,

Sin categoría , ,

Generando gráficos de temperatura en Swing

Sábado, 28 de junio de 2008

Sentilla Swing temperature display - Time Series
Ahora usted puede saber la temperatura de la sala de mi casa :)

Bueno, seguí jugando con Sentilla Perk y Swing; Esta vez terminé de acomodar al cliente gráfico que muestra los datos en una tabla, al mismo tiempo que genera gráficos de temperatura capturados por cada sensor (código fuente aquí).

Aún tengo un par de problemas:

  • No he calibrado los sensores, uno de ellos siempre mide en el rango de las 20 grados Celcius mientras que el otro está en los 30.
  • JFreeChart me tiene confundido con la gráfica, tengo un problema extraño de refrescamiento de pantalla.

Sentilla Swing temperature display - raw data in table
No es perfecto, pero los resultados son interesantes

Sin embargo no me tomó mucho tiempo montar esto, y cada vez se pone más divertido. Voy a preguntar a otros desarrolladores más expertos en el foro de Sentilla a ver que tal :)

Buscar en otros sitios:

Blogalaxia:, , , ,
Technorati:, , , ,
To2blogs:, , , ,
Del.icio.us:, , , ,

kodegeek , , , ,