Showing posts with label Java. Show all posts
Showing posts with label Java. Show all posts

Saturday, February 23, 2008

Using Aspectj and Java Annotations to Try Again

We are working on a program that interacts with a hardware device using a com port for some months. After implementing many parts of software, we found that the communication channel is not as reliable as we expected, so we decided to add some "Try Again" functionality to our code. You have seen the "Try Again" dialog many times, but have you ever taught of how to implement one?

All you need is to invoke a method, if an exception occurs ask the user "Try Again?" and if the answer is "Yes" reinvoke the method. At first it seems so simple to me but after playing with some of OO design patterns I realized that many lines of code should be modified to implement the functionality in OO, so I decided to play a little AOP.

Here is the solution I found:

The first thing we need is to specify what methods must be tried again, Thanks to Java 5 annotations, this is very easy to tag some methods, so the first thing we need is a TryAgain Java annotation:

 
 

package com.blogspot.zoftware.aspects;

 
 

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

 
 

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public @interface TryAgain {

int count() default 2;

boolean interactive() default true;

}

 
 

The count parameter specifies how many times the method should be reinvoked and the interactive parameter specifies if the user should be asked to try again or not. The second thing we need is an aspect which reinvokes the annotated methods, there are many AOP implementations in the java world but I like the annotation based aspectj the most, as it is very simple and supports standard java syntax, here is the code for the aspect:

 
 

package com.blogspot.zoftware.aspects;

 
 

import java.lang.reflect.Method;

 
 

import javax.swing.JOptionPane;

 
 

import org.aspectj.lang.ProceedingJoinPoint;

import org.aspectj.lang.annotation.Around;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.reflect.MethodSignature;

 
 

@Aspect

public class TryAgainAspect {

 
 

@Around("call(@TryAgain * *..*(..))")

public Object retry(ProceedingJoinPoint jp) throws Throwable{

Method method = ((MethodSignature)jp.getSignature()).getMethod();

TryAgain annotation = method.getAnnotation(TryAgain.class);

Throwable err = null;

 
 

for (int i = 0; i < annotation.count(); i++) {

try {

return jp.proceed();

} catch (Throwable e) {

err = e;

if(annotation.interactive() && !askRetry(e)) {

break;

}

}

}

 
 

throw err;

}

 
 

private boolean askRetry(Throwable e) {

String msg = "The following error occured:\n" + e + "\nRetry?";

return JOptionPane.showConfirmDialog(null, msg) == JOptionPane.YES_OPTION;

}

}


 

 
 

The retry advice, reinvokes every method which is tagged with @TryAgain annotation if an exception occurs (at most count times), so all you need to use this mechanism is to tag the methods that should be reinvoked on failures. Here is a sample usage:

 
 

package com.blogspot.zoftware.demo;

 
 

import com.blogspot.zoftware.aspects.TryAgain;

 
 

public class Foo {

 
 

@TryAgain(count = 3)

public void oftenFails() {

if(Math.random() < 0.6) {

throw new RuntimeException("Sorry, Failed!");

}

}

 
 

public static void main(String[] args) {

new Foo().oftenFails();

System.out.println("Passed");

}

}


 

 
 

The oftenFails method is the method that should be reinvoked, so I annotated it using @TryAgain. To run the code you need aspectj libraries (download here); you can use either dynamic (load time) or static weaving. To use load time weaving, place the following piece of XML into aop.xml under META-INF folder, then run the Foo class with -javaagent:aspectjweaver.jar command line argument.

 
 

<aspects>

<aspect name="com.blogspot.zoftware.aspects.TryAgainAspect" />

</aspects>


 

The mixture of Annotations and AspectJ is a powerful tool to solve many common software engineering problems.

Thursday, January 17, 2008

هیچ وقت به این نکته فکر کردید که اگر زبان برنامه نویسی که از آن استفاده می کنید اعداد و عملگرهای اولیه روی اعداد رو پشتیبانی نمی کردند، چطور می تونستید از اون زبان استفاده کنید؟ اصلا، به نظر شما اگر یک زبان اعداد و عملگرهای لازم رو به صورت توکار پشتیبانی نکنه، راهی برای پیاده سازی اونها وجود داره؟ شاید جالب باشه اگر بدونید که در بسیاری از زبان های برنامه نویسی تابعی (Functional Programming Languages) این قابلیت وجود داره که بدون اینکه زبان شما از اعداد یا عملگر های اولیه پشتیبانی بکنه، به راحتی اونها رو با استفاده از توابع پیاده سازی بکنید ( هر چند به خاطر کارایی معمولا این کار انجام نمی شه). امروز داشتم به این موضوع فکر می کردم که میشه این کار رو در زبان های غیر تابعی هم انجام داد یا نه؟ سعی کردم اعداد طبیعی و عملگر جمع و تفریق رو با استفاده از زبان جاوا پیاده سازی کنم. این شد که می بینید. البته یک مقدار بد قیافه به نظر می رسه.

interface NaturalNum {

boolean isZero();

NaturalNum next();

NaturalNum prev();

NaturalNum add(NaturalNum n);

NaturalNum sub(NaturalNum n);

}

class Zero implements NaturalNum {

public static final Zero INSTANCE = new Zero();

private Zero() {}

public NaturalNum add(NaturalNum n) { return n; }

public boolean isZero() { return true; }

public NaturalNum next() { return new Successor(this); }

public NaturalNum prev() { throw new RuntimeException(); }

public NaturalNum sub(NaturalNum n) {

if(n.isZero()) return this;

else throw new RuntimeException();

}

public String toString() { return ""; }

}

class Successor implements NaturalNum {

private final NaturalNum p;

public Successor(NaturalNum n) { this.p = n; }

public NaturalNum add(NaturalNum n) { return this.prev().add(n.next()); }

public boolean isZero() { return false; }

public NaturalNum next() { return new Successor(this); }

public NaturalNum prev() { return p; }

public NaturalNum sub(NaturalNum n) {

return n.isZero() ? this : this.prev().sub(n.prev());

}

public String toString() { return prev() + "1"; }

}

public class NaturalNumberTest {

public static void main(String[] args) {

NaturalNum three = Zero.INSTANCE.next().next().next();

NaturalNum four = Zero.INSTANCE.next().next().next().next();

System.out.println(three);

System.out.println(four);

System.out.println(three.add(four));

System.out.println(four.sub(three));

}

}

Saturday, May 12, 2007

volatile

در میان کلمات کلیدی زبان جاوا دو کلمه کلیدی volatile و transient خیلی مظلوم واقع شده اند و کمتر کسی هست که به آن ها بپردازد. دلیل آن هم این هست که اولا به اشتباه احساس می شود که خیلی کم به این کلمات نیاز پیدا می کنیم، ثانیا توضیح اینکه این کلمات چه کاری انجام می دهند کمی مشکل هست. در هر حال بدون هیچ دلیل خاصی تصمیم گرفتم امروز در مورد کلمه کلیدی volatile در زبان جاوا بنویسم. استفاده از کلمه کلیدی volatile در موارد زیادی ضرورت دارد. یکی از جاهایی که به این کلمه کلیدی نیاز داریم برنامه نویسی بر روی کامپیوترهای چند پردازنده است. البته استفاده نکردن از این کلمه کلیدی هنگام برنامه نویسی Multithreaded بر روی ماشینی با یک پردازنده هم می تواند ایجاد اشکال کند ولی در اینجا نمی خواهم به این موضوع بپردازم. برای اینکه کاربرد این کلمه کلیدی را درک کنیم باید تا حدودی با معماری کامپیوترهای چندپردازنده آشنا باشیم.

چندپردازنده ها عموما بسیار متنوع و پیچیده هستند، ولی گونه خاصی از آنها را که در اینجا می خواهم به آن بپردازم چندپردازنده های متقارن یا همان SMP ها هستند. در معماری SMP معمولا چند پردازنده هم شکل(متقارن) به یک حافظه مشترک دسترسی دارند. همان طور که می دانید معمولا در کامپیوترها برای تسریع دسترسی به حافظه از حافظه پنهان (یا همان Cache) استفاده می شود. استفاده از حافظه پنهان در SMP ها حداقل می تواند به دو شکل مختلف باشد:

1. تمام پردازنده ها از یک حافظه پنهان مشترک استفاده کنند، در این حالت هنگامی که یک پردازنده مقدار یک متغیر را تغییر می دهد و بلافاصله پردازنده دیگر مقدار آن را می خواند، مقدار خوانده شده با مقدار نوشته شده یکسان است.

2. هر یک از پردازنده ها حافظه پنهان خودشان را داشته باشند، در این حالت فرض کنید قرار است دو پردازنده مختلف به متغیر مشترک X دسترسی داشته باشند و این متغیر در حافظه پنهان هر دو پردازنده قرار دارد(دو مکان متفاوت). حال فرض کنید پردازنده اول مقدار X را در حافظه پنهان خود تغییر دهد و پردازنده دوم آن را از حافظه پنهان خود بخواند. از آنجایی که خواندن و نوشتن در دو مکان مختلف انجام شده ممکن است این مقدار خوانده شده با مقدار نوشته شده متفاوت باشد.

استفاده از حافظه پنهان مشترک در کامپیوترهایی که تعداد زیادی پردازنده دارند امکان پذیر نیست، چرا که در اینصورت به دلیل تعداد زیاد مراجعات به حافظه پنهان، خود حافظه پنهان به گلوگاه کارایی تبدیل شده و عملا خاصیت خود را از دست می دهد. از این رو اغلب در معماری SMP ها از شکل دوم و یا ترکیبی از شکل اول و دوم استفاده می شود.

برای حل مشکل در حالتی پردازنده ها حافظه پنهان مشترک ندارند، دو راهکار کلی وجود دارد. اول اینکه هنگامی که لازم است مقداری در حافظه پنهان یک پردازنده نوشته شود، این مقدار در حافظه پنهان دیگر پردازنده ها نیز نوشته شود. دوم اینکه پردازنده از دستورالعمل هایی پشتیبانی کند که در مواقع لازم بتوان مقدار موجود در حافظه پنهان را بطور مستقیم در حافظه اصلی نوشت و یا مقداری را بطور مستقیم و بدون توجه به مقدار موجود در حافظه پنهان، از حافظه اصلی خواند(دستور العمل های مدیریت حافظه پنهان).

در حالت آخر برنامه نویس باید برای کامپایلر مشخص کند که چه متغیرهایی به صورت مشترک بین Thread های برنامه استفاده می شود تا کامپایلر بتواند کدهای لازم برای مدیریت حافظه پنهان را تولید کند. توجه کنید که اگر قرار باشد این کار بر روی تمام متغیرهای مشترک بین Thread ها انجام شود، کارایی چند پردازنده به شدت کاهش می یابد. از این رو لازم است کاربر متغیرهایی را که لازم می داند بطور صریح مشخص کند.

فیلدهایی از کلاس که با کلمه کلیدی volatile تعریف شده اند، برای ارتباط بین Threadهای مختلف استفاده می شوند. به عبارت دقیق تر هر بار خواندن از یک فیلد volatile آخرین مقدار نوشته شده بر روی آن فیلد (توسط هر Thread و بر روی هر پردازنده ای) را بر می گرداند.

گرچه نحوه پیاده سازی این موضوع در معماری های مختلف، متفاوت است ولی بر اساس تعریفی که در JSR 133 از مدل حافظه جاوا آمده، کامپایلر و محیط زمان اجرای جاوا موظف هستند، شرایط را طوری فراهم کنند که هنگام خواندن یک فیلد volatile همواره آخرین مقداری که درون آن نوشته شده برگردانده شود.

البته دقت کنید که بجای استفاده از volatile در بسیاری از موارد می توانید از کلمه کلیدی synchronized هم استفاده کنید. همچنین دوباره بر این نکته تاکید می کنم که مشکلی که در اینجا مطرح شد حتی در ماشین های تک پردازنده هم ممکن است اتفاق بیفتد(اینجا را ببینید).

در هر حال نوشتن برنامه های Multithreaded توجه و مطالعه زیادی را می طلبد. مشکل اصلی در این برنامه ها این است که اشکالات موجود تنها در بعضی موارد خاص خود را نشان می دهد، به همین دلیل رفع اشکال آنها به سختی انجام می شود. آشنایی کامل بر مفاهیم Multithreading و تعاریف زبانی که با آن برنامه می نویسید می تواند باعث کاهش این مشکلات شود.

Friday, March 30, 2007

Vim

شاید شما بر روی کامپیوتر خود یکی از این ویرایشگرهایی که با آن همه جور فایل متنی را می توانید edit کنید دارید. Notepad++، JEdit، Ultra Edit، Crimson Editor و ... شاید بتوان صد تای دیگر از این جور ویرایشگرها نام برد. این را هم بگویم که نمی دانم اگر چنین چیزی بر روی کامپیوترتان ندارید چطور زندگی می کنید!

از بین همه ویرایشگر های موجود، من شخصا علاقه خاصی به vim دارم. vim یکی از قدرتمند ترین و متداول ترین ویرایشگرهای موجود است، اگر با Linux کار کرده باشید حتما با آن مواجه شده اید. vim ویژگی های بسیار خوب و جالبی دارد، اول اینکه می توان با آن نزدیک 500 نوع فایل متنی را ویرایش کرد که تقریبا شامل همه زبان های برنامه نویسی و بقیه فایل های متنی می شود. دوم اینکه vim تقریبا تحت هر پلتفرمی که فکرش را بکنید کار می کند، از ویندوز و لینوکس و مک گرفته تا داس و آمیگا! این برای کسانی که با پلت فرم های مختلف کار می کنند ویژگی مثبتی است. سوم اینکه vim خیلی light weight هست و خیلی سریع load می شود. چهارم اینکه vim اپن سورس است(ویژگی که حداقل برای من خیلی مهم است).

آخرین نسخه این ویرایشگر را می توانید از اینجا دانلود کنید. اگر با ویندوز کار می کنید توصیه می کنم installer آن را دانلود کنید(self-installing executable) ولی اگر با لینوکس کار می کنید به احتمال زیاد این ویرایشگر قبلا بر روی سیستم عامل شما نصب شده.

در حالت پیش فرض vim یک ویرایشگر حرفه ای است و برای شروع به کار نیاز به کمی آموزش دارد ولی خوشبختانه آنقدر انعطاف پذیر هست که بتوان آن را همان طور که دوست داریم پیکر بندی کنیم. برای این کار اگر در محیط ویندوز هستید به پوشه نصب vim بروید(معمولا program files\vim) و فایل _vimrc را باز کنید(در لینوکس معمولا این فایل با نام .vimrc و در پوشه کاربر قرار دارد). سپس خطوط زیر را به انتهای این فایل اضافه کنید:

color blue

set nobackup

set guifont=Courier_New:h12

set encoding=utf-8

set insertmode

set spell

set nowrap

set guioptions+=r

set guioptions+=b

خط color blue رنگ های پیش زمینه و پس زمینه را به گونه ای که در colorschema ای با نام blue تعریف شده تغییر می دهد. به جای blue می توانید اسم هر کدام از فایل هایی را که در پوشه colors واقع در پوشه نصب vim موجود است استفاده کنید. توصیه می کنم colorschema را تغییر دهید تا یکی را که به نظرتان مناسب است پیدا کنید.

وقتی فایلی را تغییر می دهید، بصورت پیش فرض vim یک نسخه از فایل قدیمی را به عنوان backup نگه می دارد. این نسخه بطور پیش فرض در کنار همان فایل جدید نگه داشته می شود. این کار باعث شلوغ شدن دیسک می شود. خط set nobackup باعث می شود که دیگر backup نگه داشته نشود. اگر به جای این خط از set backupdir=c:\\backups استفاده کنید vip تمام backup ها را در پوشه ای که مشخص کرده اید ذخیره می کند.

همان طور که مشخص است set guifont=Courier_New:h12 فونت و اندازه متن را در ویرایشگر مشخص می کند.

خط set insertmode باعث می شود vim در مد insert شروع شود. vim از مدهای مختلفی پشتیبانی می کند که برای مقاصد مختلفی استفاده می شود ولی مد insert خیلی شبیه حالت عادی ویرایشگرهای دیگر است.

خط set spell، spell checking را در vim فعال می کند.

خطوط set guioptions+=r و set guioptions+=b باعث می شود vim در سمت راست و پایین scrollbar هایی را نشان دهد.

اگر در محیط ویندوز از installer استفاده کرده باشد، یک گزینه Edit with Vim به منوی Windows Explorer اضافه شده است. با استفاده از این گزینه بر روی هر فایلی که کلیک راست کنید می توانید آنرا توسط vim باز کنید.

Sunday, December 17, 2006

JDK6 بالاخره Release شد(البته تقریبا 5 روز پیش بود، دوازدهم دسامبر). برای دانلود آن می توانید به آدرس https://java.sun.com/javase/6 مراجعه کنید. اگر داخل ایران هستید احتمالا برای دانلود آن با مشکل مواجه خواهید شد!

Monday, November 20, 2006

پشتیبانی جاوا از زبان های اسکریپتی (2)

در پست قبلی در مورد قابلیت تعامل جاوا با زبان های اسکریپتی در JDK 6 و قدرت بالای این ویژگی گفتم. در این پست برای اینکه موضوع بیشتر مشخص شود یک مثال کاربردی آورده ام. احتمالا در پست بعدی مثال دیگری را خواهم آورد.

قابلیت گسترش

امروزه برنامه هایی که می شود به آنها plug-in اضافه کرد یا آنها را به نحوی customize کرد خیلی متداول شده. خیلی خوب است اگر کاربر بتواند برنامه ای را که در دست دارد به نحوی آنطور که می خواهد تغییر دهد یا چیزی به آن اضافه کند. اضافه کردن چنین ویژگی هایی به یک برنامه در جاوا دو راه حل کلی(حداقل تا آنجا که من می دانم) دارد. یک راه حل مشکل و یک راه حل ساده. راه حل مشکل همان راه حلی است که eclipse و بسیاری دیگر از IDE ها و برنامه های تجاری از آن استفاده می کنند و آن هم اضافه کردن این قابلیت با استفاده از تعریف یک سری extension point است. توسعه دهندگان می توانند با استفاده از پروتکلی که برنامه اصلی تعریف می کند extension های خود را به برنامه اضافه کنند(این پروتکل معمولا بصورت یک DTD یا یک چیزی شبیه به آن به علاوه یک سری interface جاوا هست). برنامه اصلی معمولا از Java Reflection برای اضافه کردن extension ها به خود استفاده می کند.

ولی راه حل ساده استفاده از یک زبان اسکریپتی به همراه جاوا هست(همان کاری که jEdit انجام می دهد). در این راه حل قسمت عمده برنامه با استفاده از جاوا نوشته و کامپایل می شود و قسمت کوچکی از برنامه می تواند (بعدا) توسط یک زبان اسکریپتی نوشته و به برنامه اضافه شود. هر کدام از این روش ها مزایا و معایب خاص خود را دارد ولی در اینجا نمی خواهم به آنها بپردازم. کدی که در ادامه آورده شده یک مثال بسیار ساده(ولی کامل) از این روش است.

package com.blogspot.zoftware.jdk6.script;

import java.awt.BorderLayout;

import java.io.File;

import java.io.FileReader;

import javax.script.ScriptEngine;

import javax.script.ScriptEngineManager;

import javax.swing.JButton;

import javax.swing.JFrame;

public class ScriptedUI extends JFrame {

private JButton button = new JButton("Click Here");

public ScriptedUI() {

super("Script Demo");

setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

setLayout(new BorderLayout());

add(button);

setSize(180, 70);

}

public JButton getButton() {

return button;

}

static void runScripts(ScriptEngine eng, String folder) {

File[] scripts = new File(folder).listFiles();

for (File script : scripts) {

if (script.getName().endsWith(".js")) {

try {

eng.eval(new FileReader(script));

} catch (Exception e) {

e.printStackTrace();

}

}

}

}

public static void main(String[] args) {

ScriptEngineManager factory = new ScriptEngineManager();

ScriptEngine bootstrapEng = factory.getEngineByName("JavaScript");

runScripts(bootstrapEng, "scripts/bootstrap");

ScriptedUI frame = new ScriptedUI();

ScriptEngine startupEng = factory.getEngineByName("JavaScript");

startupEng.put("frame", frame);

runScripts(startupEng, "scripts/startup");

frame.setVisible(true);

}

}

این برنامه هنگام شروع ابتدا هر اسکریپتی را که در پوشه scripts/bootstrap و با پسوند js وجود داشته باشد می خواند و اجرا می کند. سپس یک فریم می سازد و بعد از آن اسکریپت های پوشه scripts/startup را اجرا می کند. در اسکریپت های پوشه scripts/startup متغیر frame تعریف شده هست.

http://photos1.blogger.com/blogger/1266/3268/320/scripts.0.png

اگر در پوشه های starup و bootstrap فایلی وجود نداشته باشد، فریم نشان داده شده با LookAndFeel پیش فرض جاوا (Metal) و با اندازه مشخص شده در برنامه نشان داده می شود. حالا فرض کنیم بخواهیم LookAndFeel را تغییر دهیم. کافی است در پوشه scripts/bootstrap یک فایل با پسوند js قرار داده و داخل آن کد جاوا اسکریپت زیر را بنویسیم.

/* folder: scripts/bootstrap */
importPackage(javax.swing);
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());

با اضافه کردن این فایل و اجرای مجدد برنامه شکل فریم تغییر می کند(شکل 2) و LookAndFeel برنامه همانند بقیه برنامه های سیستم می شود. اگر به کد برنامه دقت کرده باشید در خط 48 متغیر frame به موتور اسکریپت پاس می شود(به این معنی که این متغیر درون اسکریپت های پوشه startup شناخته شده است). حال اگر یک فایل با محتویات زیر درون پوشه scripts/startup قرار دهیم شکل فریم تغییر کرده و به شکل 3 در می آید. همچنین این فایل یک ActionListener به Button اضافه می کند که هنگام کلیک بر روی آن یک MessageBox به نمایش در می آید(شکل 4).

/* folder: scripts/startup */

frame.button.text = 'Do Not Click Please';

frame.setSize(200, 70);

importPackage(javax.swing);

listener = function(e){ JOptionPane.showMessageDialog(frame,

"You Clicked on " + e.source.text); }

frame.button.addActionListener(listener);

توجه دارید که بدون استفاده از زبان اسکریپتی، برای گسترش برنامه باید سورس آن را تغییر دهیم و دوباره آنرا کامپایل کنیم ولی با استفاده از این روش نیازی به کامپایلر نداریم. با استفاده از این روش، کاربر به آسانی می تواند محیط گرافیکی برنامه را تغییر داده و کامپوننت ها و منو های جدید به آن اضافه کند. البته این روش فقط خاص محیط گرافیکی نیست و می توان در بسیاری از موارد دیگر مانند نوشتن فایل های Configuratin برای برنامه نیز از آن استفاده کرد.

مثالی که در اینجا آورده شد بدون هیچ تغییر و نیاز به کتابخانه اضافی در JDK 6 اجرا می شود ولی اگر لازم باشد در ویرایش های قدیمی تر JDK اجرا شود نیاز به کمی تغییر و اضافه کردن کتابخانه های مورد نیاز است.

نکته آخر اینکه همیشه لازم نیست که extension ها کاملا به زبان اسکریپتی نوشته شود و می توان خود extension را با جاوا نوشت و کامپایل کرد و از زبان اسکریپتی بصورت یک چسپ برای اتصال extension ها استفاده کرد.